1.Data exploration + Logistic Regression

a.Describe the data: sample size n, number of predictors p, and number of observations in each class.

##------------------------------------ 
# a. 
wdbc %>% summarise(n=n())
wdbc %>% group_by(V2) %>% summarise(n=n(), )
glimpse(wdbc)

b.Divide the data into a training set of 400 observations, and a test set; from now on, unless specified, work only on the training set.

##------------------
## b.
## Divide the data into a training set of 400 obs
set.seed(101)
train_id_wdbc = sample(nrow(wdbc), 400)
train_wdbc = wdbc[train_id_wdbc, ]
test_wdbc  = wdbc[-train_id_wdbc, ]

c.Make a scatterplot displaying Y (color or shape encoded) and the predic- tors X1, X2 (on the x- and y-axis). Based on this scatterplot, do you think it will be possible to accurately predict the outcome from the predictors? Motivate your answer.

Based on the plot, we conclude that the average radius is a better predictor for the diagnosis as the blue points (diagnosis with malignant) tend to have higher values and the red points (diagnosis with benign) tend to have lower values, while the average texture does not seem to be a strong predictor as the red points and the blue points are spread evenly.

d.Fit a logistic regression model to predict Y and make a table, displaying the coefficient estimates, standard errors, and p-values (use command summary). Give an interpretation of the values of the coefficient estimates.

##------------------
## logistic model
## transfer M variable from character to numeric (0,1)
train_wdbc$Dig <- ifelse (train_wdbc$V2=="M", 1, 0)

glm.wdbc = glm(Dig ~ V3 + V4, family = binomial(link = "logit"), data = train_wdbc)
summary(glm.wdbc)
kable(summary(glm.wdbc)$coefficients)
Estimate Std. Error z value Pr(>|z|)
(Intercept) -21.3813316 2.3238811 -9.200699 0e+00
V3 1.1232747 0.1304202 8.612734 0e+00
V4 0.2474737 0.0485324 5.099144 3e-07

e.Use the coefficient estimates to manually calculate the predicted probabil- ity P(Y = M | (X1,X2) = (10,12)) writing explicitly every step. Compare your results with the prediction computed with predict.

##------------------
## e.
## extract the coefficients
beta_0 <- coef(glm.wdbc)[1]
beta_1 <- coef(glm.wdbc)[2]
beta_2 <- coef(glm.wdbc)[3]

## calculate the linear predictor
pred <- beta_0 + beta_1*10 + beta_2*12

## calculate the predicted probability
prob <- 1 / (1 + exp(-pred))
prob
# 0.0007619246

# compare with the prediction computed using predict
predict(glm.wdbc, data.frame(V3 = 10, V4 = 12), type = "response")
# 0.0007619246

# The two values are the same. 

f.Use the glm previously fitted and the Bayes rule to compute the pre- dicted outcome Yˆ from the associated probability estimates (computed with predict) both on the training and the test set. Then compute the confusion table and prediction accuracy (rate of correctly classified obser- vations) both on the training and test set. Comment on the results.

The model accuracy for training data and the one for testing data are similar. It indicates that the good model performance.

##------------------
## f.
## Compute the predicted probabilities using the predict function
train_probs <- predict(glm.wdbc, newdata = train_wdbc, type = "response")
test_probs <- predict(glm.wdbc, newdata = test_wdbc, type = "response")

## Use the Bayes rule to compute the predicted outcome 
train_prediction <- ifelse(train_probs > 0.5, "M", "B")
test_prediction <- ifelse(test_probs > 0.5, "M", "B")

## Create confusion tables on the training and test sets
train_confusion_table <- table(Prediction = train_prediction, Actual = train_wdbc$V2)
test_confusion_table <- table(Prediction = test_prediction, Actual = test_wdbc$V2)

## Calculate prediction accuracy on the training and test sets
train_accuracy <- sum(diag(train_confusion_table)) / nrow(train_wdbc)
test_accuracy <- sum(diag(test_confusion_table)) / nrow(test_wdbc)
kable(train_confusion_table,
      caption = "Confusion Table for training set")
Confusion Table for training set
B M
B 233 23
M 15 129
kable(test_confusion_table, 
      caption = "Confusion Table for testing set")
Confusion Table for testing set
B M
B 101 12
M 8 48

g.Plot an image of the decision boundary as follows:1.Generate a dense set (e.g. 10000 observations) of possible values for the predictors (X1,X2) within reasonable ranges; (the command expand.grid might come in handy); 2.Use the glm model previously fitted to predict the outcome probabil- ities for every observation you have generated and use Bayes rule to compute the predicted outcomes; 3.Plot predicted outcomes (color coded) and associated predictors in a 2D scatter plot together with the training set.Generate the same plot for probability cutoff values of 0.25 and 0.75. Comment on the results.

The increase in the red region (predicted as benign tumor) as the cutoff value increases. When the cutoff value is low, the model would be more conservative in classifying an observation as benign, while as the cutoff value increases, the model becomes more confident in classifying an observation as benign. Therefore, the red region would increase with higher cutoff value.

## g.
## specify reasonable ranges for X1 and X2
x1_range <- c(min(train_wdbc$V3), max(train_wdbc$V3))
x2_range <- c(min(train_wdbc$V4), max(train_wdbc$V4))

## create a dense set of possible values for X1 and X2 using expand.grid
dense_set <- expand.grid(V3 = seq(from = x1_range[1], to = x1_range[2], length.out = 100),
                         V4 = seq(from = x2_range[1], to = x2_range[2], length.out = 100))

## use the fitted GLM model to predict the outcome probabilities for each observation in the dense set
dense_set$probs <- predict(glm.wdbc, newdata = dense_set, type = "response")

## compute the predicted outcomes using Bayes rule
dense_set$prediction <- ifelse(dense_set$probs > 0.5, "M", "B")

h.Plot the ROC curve, computed on the test set.

i.Compute an estimate of the Area under the ROC Curve (AUC).

The AUC score is 0.9398.

2.Linear Discriminant Analysis Model

a.Now fit a linear discriminant analysis model to the training set you created in Exercise 1. Make a table displaying the estimated ‘Prior probabilities of groups’ and ‘Group means’. Describe in words the meaning of these estimates and how they are related to the posterior probabilities.

The estimated group means are the average of the predictor variables (radius or texture) for each group (malignant or benign). These estimates are used to calculate the posterior probabilities which is the probability of an observation belonging to malignant or benign given its estimated predictor values. This calculation is based on Bayes’ theorem, which combines prior knowledge (prior probabilities) and new information (estimated predictor values) to estimate the probability of an event.

##------------------------------------ LDA
## a.
## linear discriminant model
lda.wdbc = lda(Dig ~ V3 + V4, data = train_wdbc, center = TRUE) 

lda_table <- cbind(lda.wdbc$prior, lda.wdbc$mean)
colnames(lda_table) <- c("Prior prob of groups", "Group means(radius)", "Group means(texture)")
kable(lda_table)
Prior prob of groups Group means(radius) Group means(texture)
0 0.62 12.15901 17.87661
1 0.38 17.46270 21.68230

b.Use the fitted model and Bayes rule to compute the predicted outcome Yˆ from the predicted posterior probabilities, both on the training and test set. Then, compute the confusion table and prediction accuracy both on the training and test set. Comment on the results.

The two prediction accuracy are similar, it indicates that the model has a good performance. The values are slightly smaller than the accuracy from fitting the logistic model. LDA finds the optimal linear combinations of predictors that maximize the separation between classes and then uses Bayes’ theorem to make predictions, while logistic regression uses maximum likelihood estimation to fit a logistic function to the data. In this scenario, it seems that the logistic model is more appropriate because LDA with linear combination might be relative simpler for the data.

##------------------
## b
X_train <- train_wdbc %>% select(c(V3, V4))
X_test <- test_wdbc %>% select(c(V3, V4))

## fit the model and calculate post prob
train_pred.lda <- predict(lda.wdbc, X_train)$posterior
test_pred.lda <- predict(lda.wdbc, X_test)$posterior

## compute outcome Y based on the post prob
train_outcome.lda <- apply(train_pred.lda, 1, which.max)
## outcome variable = 1 or 2 
## transfer them to B or M
train_outcome.lda <- data.frame(train_outcome.lda)
train_outcome.lda$train_outcome.lda <- 
        ifelse(train_outcome.lda$train_outcome.lda==1, "B", "M")

## Do it again, but using testing set
test_outcome.lda <- apply(test_pred.lda, 1, which.max)
## outcome variable = 1 or 2 
## transfer them to B or M
test_outcome.lda <- data.frame(test_outcome.lda)
test_outcome.lda$test_outcome.lda <- 
        ifelse(test_outcome.lda$test_outcome.lda==1, "B", "M")

## confusion matrix
train_confusion.lda <- table(Prediction = train_outcome.lda$train_outcome.lda, 
                             Actual = train_wdbc$V2)
test_confusion.lda <- table(Prediction = test_outcome.lda$test_outcome.lda, 
                            Actual = test_wdbc$V2)

## prediction accuracy
train_accuracy.lda <- sum(diag(train_confusion.lda)) / nrow(train_wdbc)
test_accuracy.lda <- sum(diag(test_confusion.lda)) / nrow(test_wdbc)
kable(train_confusion.lda,
      caption = "Confusion table for training set - LDA")
Confusion table for training set - LDA
B M
B 241 37
M 7 115
kable(test_confusion.lda,
      caption = "Confusion table for testing set - LDA")
Confusion table for testing set - LDA
B M
B 104 16
M 5 44

c.Plot an image of the LDA decision boundary (following the steps in 1(g)). Generate the same plot for cutoff values of 0.25 and 0.75. Comment on the results.

The increase in the red region (predicted as benign tumor) as the cutoff value decreases When the cutoff value is high, the model would be more conservative in classifying an observation as benign, while as the cutoff value decrease, the model becomes more confident in classifying an observation as benign.

##------------------
## c.
## use the fitted model to predict the outcome probabilities for each observation in the dense set
probs.lda <- predict(lda.wdbc, newdata = dense_set, type = "response")$posterior

# Compute the predicted outcomes using Bayes rule with different probability cutoff values
pred_outcomes_05 = ifelse(probs.lda[,1] > probs.lda[,2], "B", "M")
pred_outcomes_25 = ifelse(probs.lda[,1] > 0.25, "B", "M")
pred_outcomes_75 = ifelse(probs.lda[,1] > 0.75, "B", "M")

d.Plot the ROC curve, computed on the test set.

e.Compute an estimate of the Area under the ROC Curve (AUC).

The AUC score is 0.9396

3.Quadratic Discriminant Analysis Model.

a.Now fit a linear discriminant analysis model to the training set you created in Exercise 1. Make a table displaying the estimated ‘Prior probabilities of groups’ and ‘Group means’. Describe in words the meaning of these estimates and how they are related to the posterior probabilities.

The estimated group means are the average of the predictor variables (radius or texture) for each group (malignant or benign). These estimates are used to calculate the posterior probabilities which is the probability of an observation belonging to malignant or benign given its estimated predictor values. This calculation is based on Bayes’ theorem, which combines prior knowledge (prior probabilities) and new information (estimated predictor values) to estimate the probability of an event.

## a.
## quadratic discriminant analysis 
qda.wdbc = qda(Dig ~ V3 + V4, data = train_wdbc, center = TRUE)
qda.wdbc
qda_table <- cbind(qda.wdbc$prior, qda.wdbc$mean)
colnames(qda_table) <- c("Prior prob of groups", "Group means(radius)", "Group means(texture)")
kable(qda_table)
Prior prob of groups Group means(radius) Group means(texture)
0 0.62 12.15901 17.87661
1 0.38 17.46270 21.68230

b.Use the fitted model and Bayes rule to compute the predicted outcome Yˆ from the predicted posterior probabilities, both on the training and test set. Then, compute the confusion table and prediction accuracy both on the training and test set. Comment on the results.

The two prediction accuracy are similar, which indicates that the model fits well. The values are similar to the ones from LDA. It seems that using LDA or QDA is not so much different.

##------------------
## b.
## fit the model and calculate post prob
train_pred.qda <- predict(qda.wdbc, X_train)$posterior
test_pred.qda <- predict(qda.wdbc, X_test)$posterior

## compute outcome Y based on the post prob on training set
train_outcome.qda <- apply(train_pred.qda, 1, which.max)
## outcome variable = 1 or 2 
## transfer them to B or M
train_outcome.qda <- data.frame(train_outcome.qda)
train_outcome.qda$train_outcome.qda <- 
        ifelse(train_outcome.qda$train_outcome.qda==1, "B", "M")

## compute outcome Y based on the post prob on testing set
test_outcome.qda <- apply(test_pred.qda, 1, which.max)
## outcome variable = 1 or 2 
## transfer them to B or M
test_outcome.qda <- data.frame(test_outcome.qda)
test_outcome.qda$test_outcome.qda <- 
        ifelse(test_outcome.qda$test_outcome.qda==1, "B", "M")

## confusion matrix
train_confusion.qda <- table(Prediction = train_outcome.qda$train_outcome.qda, 
                             Actual = train_wdbc$V2)
test_confusion.qda <- table(Prediction = test_outcome.qda$test_outcome.qda, 
                            Actual = test_wdbc$V2)

## prediction accuracy
train_accuracy.qda <- sum(diag(train_confusion.qda)) / nrow(train_wdbc)
test_accuracy.qda <- sum(diag(test_confusion.qda)) / nrow(test_wdbc)
kable(train_confusion.qda,
      caption = "Confusion table for training set - QDA")
Confusion table for training set - QDA
B M
B 236 34
M 12 118
kable(test_confusion.qda,
      caption = "Confusion table for testing set - QDA")
Confusion table for testing set - QDA
B M
B 101 14
M 8 46

c.(c) Plot an image of the QDA decision boundary (following the steps in 1(g)). Generate the same plot for cutoff values of 0.25 and 0.75. Comment on the results.

The increase in the red region (predicted as benign tumor) as the cutoff value decreases When the cutoff value is high, the model would be more conservative in classifying an observation as benign, while as the cutoff value decrease, the model becomes more confident in classifying an observation as benign.

##------------------
## c.
## use the fitted GLM model to predict the outcome probabilities for each observation in the dense set
probs.qda <- predict(qda.wdbc, newdata = dense_set, type = "response")$posterior

# Compute the predicted outcomes using Bayes rule with different probability cutoff values
pred_outcomes_05.qda = ifelse(probs.qda[,1] > probs.qda[,2], "B", "M")
pred_outcomes_25.qda = ifelse(probs.qda[,1] > 0.25, "B", "M")
pred_outcomes_75.qda = ifelse(probs.qda[,1] > 0.75, "B", "M")

d.Plot the ROC curve, computed on the test set.

e.Compute an estimate of the Area under the ROC Curve (AUC).

The AUC score is 0.9361

4.KNN Classifier.

a.For all choices of k = {1, 2, 3, 4, 20} (number of neighbors), compute the predicted outcome Yˆ for each observation in the training and test set. Then, compute the confusion table and prediction accuracy both on the training and test set. Comment on the results.

##------------------------------------ KNN
## a.
## knn 
k_values <- c(1, 2, 3, 4, 20)
for (k in k_values) {
        # train set
        knn.train.label <- knn(train = train_wdbc[,c("V3", "V4")], 
                               test = train_wdbc[,c("V3", "V4")], 
                               cl = train_wdbc$V2, k = k)
        
        # confusion table 
        confusion_table_train <- table(knn.train.label, train_wdbc$V2)
        # prediction accuracy
        accuracy_train <- sum(diag(confusion_table_train)) / sum(confusion_table_train)
        
        # test set
        knn.test.label <- knn(train = train_wdbc[,c("V3", "V4")], 
                              test = test_wdbc[,c("V3", "V4")], 
                              cl = train_wdbc$V2, k = k)
        
        # confusion table - test set
        confusion_table_test <- table(knn.test.label, test_wdbc$V2)
        # prediction accuracy
        accuracy_test <- sum(diag(confusion_table_test)) / sum(confusion_table_test)
        
        # Print the results
        cat("For k =", k, ":\n")
        cat("Training set confusion table:\n")
        print(confusion_table_train)
        cat("Training set accuracy:", accuracy_train, "\n")
        cat("Test set confusion table:\n")
        print(confusion_table_test)
        cat("Test set accuracy:", accuracy_test, "\n\n")
}

b.Plot an image of the decision boundary (following the steps in 1(g)), for k = {1, 2, 3, 4, 20} (number of neighbors). Comment on the results.

Based on the KNN decision boundary plots with different values of k, we can see that the boundaries are less specific than the boundaries using logistic regression, LDA, and QDA. It makes sense because KNN models make predictions based on the classes of the nearest neighbors, whereas logistic regression, LDA, and QDA are based on a more sophisticated mathematical model. In addition, according to the five plots with different values of k, we know that the decision boundary is more precise when k=3. It seems that k=3 might be more appropriate in this scenario.

c.Compute and plot the prediction accuracy (on both the training and test set), for k = {1,2,…,19,20} (number of neighbors). Which value of k would you choose? Comment on the results.

I would choose k=3. The followings are my reasons. Based on the plot of prediction accuracy for different value of k on the training set, we can see that the accuracy decrease as the k value increases. With the elbow method, the elbow point is at k=2 or k=3. In addition, based on the prediction accuracy for different values of k on the testing, we can see that the accuracy at k=3, k=6, k=17, k=18, k=19, and k=20 are relatively high. Therefore, I would choose k=3 as the best k value.

##------------------
## c.
## plot the prediction accuracy 
train_accuracy = numeric(20)
test_accuracy = numeric(20)

for (i in 1:20) {
        knn.label <- knn(train = train_wdbc[,c("V3","V4")], 
                        test = train_wdbc[,c("V3","V4")], 
                        cl = train_wdbc$V2, k = i)
        train_accuracy[i] <- mean(knn.label == train_wdbc$V2)
        test_accuracy[i] <- mean(knn.label == test_wdbc$V2)
}

for (i in 1:20) {
        knn.label <- knn(train = train_wdbc[,c("V3", "V4")], 
                         test = test_wdbc[,c("V3", "V4")], 
                         cl = train_wdbc$V2, k = i)
        test_accuracy[i] <- mean(knn.label == test_wdbc$V2)
}

Code Appendix

knitr::opts_chunk$set(echo = TRUE)
knitr::opts_chunk$set(warning = FALSE, message = FALSE) 
## load the libraries
library(knitr)
library(ggplot2)
library(MASS)  # Implements LDA & QDA
library(caret) # confusionMatrix
library(pROC)  # ROC & AUC
library(class) # Implements KNN
library(dplyr)
library(GGally)

##------------------------------------
## load the dataset
wdbc <- read.csv("wdbc.data", header = FALSE)
head(wdbc)
## select column {2,3,4}
wdbc <- wdbc %>% select(c("V2", "V3", "V4"))

##------------------------------------ 
# a. 
wdbc %>% summarise(n=n())
wdbc %>% group_by(V2) %>% summarise(n=n(), )
glimpse(wdbc)
##------------------
## b.
## Divide the data into a training set of 400 obs
set.seed(101)
train_id_wdbc = sample(nrow(wdbc), 400)
train_wdbc = wdbc[train_id_wdbc, ]
test_wdbc  = wdbc[-train_id_wdbc, ]
##------------------
## c.
## scatterplot
train_wdbc %>% ggplot(aes(x = V3, y = V4, color = factor(V2))) + 
        geom_point() + 
        xlab("Average radius of the cell nuclei") + ylab("Average texture of the cell nuclei") + 
        ggtitle("Scatterplot of radius and texure with color encoded diagnosis")

##------------------
## logistic model
## transfer M variable from character to numeric (0,1)
train_wdbc$Dig <- ifelse (train_wdbc$V2=="M", 1, 0)

glm.wdbc = glm(Dig ~ V3 + V4, family = binomial(link = "logit"), data = train_wdbc)
summary(glm.wdbc)

kable(summary(glm.wdbc)$coefficients)
##------------------
## e.
## extract the coefficients
beta_0 <- coef(glm.wdbc)[1]
beta_1 <- coef(glm.wdbc)[2]
beta_2 <- coef(glm.wdbc)[3]

## calculate the linear predictor
pred <- beta_0 + beta_1*10 + beta_2*12

## calculate the predicted probability
prob <- 1 / (1 + exp(-pred))
prob
# 0.0007619246

# compare with the prediction computed using predict
predict(glm.wdbc, data.frame(V3 = 10, V4 = 12), type = "response")
# 0.0007619246

# The two values are the same. 
##------------------
## f.
## Compute the predicted probabilities using the predict function
train_probs <- predict(glm.wdbc, newdata = train_wdbc, type = "response")
test_probs <- predict(glm.wdbc, newdata = test_wdbc, type = "response")

## Use the Bayes rule to compute the predicted outcome 
train_prediction <- ifelse(train_probs > 0.5, "M", "B")
test_prediction <- ifelse(test_probs > 0.5, "M", "B")

## Create confusion tables on the training and test sets
train_confusion_table <- table(Prediction = train_prediction, Actual = train_wdbc$V2)
test_confusion_table <- table(Prediction = test_prediction, Actual = test_wdbc$V2)

## Calculate prediction accuracy on the training and test sets
train_accuracy <- sum(diag(train_confusion_table)) / nrow(train_wdbc)
test_accuracy <- sum(diag(test_confusion_table)) / nrow(test_wdbc)

kable(train_confusion_table,
      caption = "Confusion Table for training set")
kable(test_confusion_table, 
      caption = "Confusion Table for testing set")
## g.
## specify reasonable ranges for X1 and X2
x1_range <- c(min(train_wdbc$V3), max(train_wdbc$V3))
x2_range <- c(min(train_wdbc$V4), max(train_wdbc$V4))

## create a dense set of possible values for X1 and X2 using expand.grid
dense_set <- expand.grid(V3 = seq(from = x1_range[1], to = x1_range[2], length.out = 100),
                         V4 = seq(from = x2_range[1], to = x2_range[2], length.out = 100))

## use the fitted GLM model to predict the outcome probabilities for each observation in the dense set
dense_set$probs <- predict(glm.wdbc, newdata = dense_set, type = "response")

## compute the predicted outcomes using Bayes rule
dense_set$prediction <- ifelse(dense_set$probs > 0.5, "M", "B")
## Plot the predicted outcomes for probability cutoff of 0.5
ggplot(data = dense_set, aes(x = V3, y = V4, color = factor(prediction))) +
        geom_point(alpha = 0.2) +
        geom_point(data = train_wdbc, aes(x = V3, y = V4, color = factor(V2))) +
        scale_color_discrete(name = "Prediction") +
        ggtitle("Logistic Regression Decision Boundary (Cutoff = 0.5)") +
        xlab("Average radius") + ylab("Average texture")
dense_set$prediction_25 <- ifelse(dense_set$probs > 0.25, "M", "B")
ggplot(data = dense_set, aes(x = V3, y = V4, color = factor(prediction_25))) +
        geom_point(alpha = 0.2) +
        geom_point(data = train_wdbc, aes(x = V3, y = V4, color = factor(V2))) +
        scale_color_discrete(name = "Prediction") +
        ggtitle("Logistic Regression Decision Boundary (Cutoff = 0.25)") +
        xlab("Average radius") + ylab("Average texture")
dense_set$prediction_75 <- ifelse(dense_set$probs > 0.75, "M", "B")
ggplot(data = dense_set, aes(x = V3, y = V4, color = factor(prediction_75))) +
        geom_point(alpha = 0.2) +
        geom_point(data = train_wdbc, aes(x = V3, y = V4, color = factor(V2))) +
        scale_color_discrete(name = "Prediction") +
        ggtitle("Logistic Regression Decision Boundary (Cutoff = 0.75)") +
        xlab("Average radius") + ylab("Average texture")
##------------------
## h.
test_probs <- predict(glm.wdbc, newdata = test_wdbc, type = "response")
## calculate AUC score
roc_score = roc(response = test_wdbc$V2, predictor = test_probs)  
#roc_score

## plot the ROC curve
ggroc(roc_score, linetype=1, size = 1) + 
        xlab("FPR") + ylab("TPR") +
        ggtitle("Test ROC curve for the Logistic Regression Model")

##------------------------------------ LDA
## a.
## linear discriminant model
lda.wdbc = lda(Dig ~ V3 + V4, data = train_wdbc, center = TRUE) 

lda_table <- cbind(lda.wdbc$prior, lda.wdbc$mean)
colnames(lda_table) <- c("Prior prob of groups", "Group means(radius)", "Group means(texture)")

kable(lda_table)
##------------------
## b
X_train <- train_wdbc %>% select(c(V3, V4))
X_test <- test_wdbc %>% select(c(V3, V4))

## fit the model and calculate post prob
train_pred.lda <- predict(lda.wdbc, X_train)$posterior
test_pred.lda <- predict(lda.wdbc, X_test)$posterior

## compute outcome Y based on the post prob
train_outcome.lda <- apply(train_pred.lda, 1, which.max)
## outcome variable = 1 or 2 
## transfer them to B or M
train_outcome.lda <- data.frame(train_outcome.lda)
train_outcome.lda$train_outcome.lda <- 
        ifelse(train_outcome.lda$train_outcome.lda==1, "B", "M")

## Do it again, but using testing set
test_outcome.lda <- apply(test_pred.lda, 1, which.max)
## outcome variable = 1 or 2 
## transfer them to B or M
test_outcome.lda <- data.frame(test_outcome.lda)
test_outcome.lda$test_outcome.lda <- 
        ifelse(test_outcome.lda$test_outcome.lda==1, "B", "M")

## confusion matrix
train_confusion.lda <- table(Prediction = train_outcome.lda$train_outcome.lda, 
                             Actual = train_wdbc$V2)
test_confusion.lda <- table(Prediction = test_outcome.lda$test_outcome.lda, 
                            Actual = test_wdbc$V2)

## prediction accuracy
train_accuracy.lda <- sum(diag(train_confusion.lda)) / nrow(train_wdbc)
test_accuracy.lda <- sum(diag(test_confusion.lda)) / nrow(test_wdbc)

kable(train_confusion.lda,
      caption = "Confusion table for training set - LDA")

kable(test_confusion.lda,
      caption = "Confusion table for testing set - LDA")

##------------------
## c.
## use the fitted model to predict the outcome probabilities for each observation in the dense set
probs.lda <- predict(lda.wdbc, newdata = dense_set, type = "response")$posterior

# Compute the predicted outcomes using Bayes rule with different probability cutoff values
pred_outcomes_05 = ifelse(probs.lda[,1] > probs.lda[,2], "B", "M")
pred_outcomes_25 = ifelse(probs.lda[,1] > 0.25, "B", "M")
pred_outcomes_75 = ifelse(probs.lda[,1] > 0.75, "B", "M")

## Plot the predicted outcomes for probability cutoff of 0.5
ggplot(data = dense_set, aes(x = V3, y = V4, color = factor(pred_outcomes_05))) +
        geom_point(alpha = 0.2) +
        geom_point(data = train_wdbc, aes(x = V3, y = V4, color = factor(V2))) +
        scale_color_discrete(name = "Prediction") +
        ggtitle("LDA Decision Boundary (Cutoff = 0.5)") +
        xlab("Average radius") + ylab("Average texture")
# Plot the predicted outcomes for a probability cutoff of 0.25
ggplot(data = dense_set, aes(x = V3, y = V4, color = factor(pred_outcomes_25))) +
        geom_point(alpha = 0.2) +
        geom_point(data = train_wdbc, aes(x = V3, y = V4, color = factor(V2))) +
        scale_color_discrete(name = "Prediction") +
        ggtitle("LDA Decision Boundary (Cutoff = 0.25)") +
        xlab("Average radius") + ylab("Average texture")
# Plot the predicted outcomes for a probability cutoff of 0.75
ggplot(data = dense_set, aes(x = V3, y = V4, color = factor(pred_outcomes_75))) +
        geom_point(alpha = 0.2) +
        geom_point(data = train_wdbc, aes(x = V3, y = V4, color = factor(V2))) +
        scale_color_discrete(name = "Prediction") +
        ggtitle("LDA Decision Boundary (Cutoff = 0.75)") +
        xlab("Average radius") + ylab("Average texture")
##------------------
## d.
test.lda_probs <- predict(lda.wdbc, newdata = test_wdbc,  type = "response")$posterior[, 2]
## calculate AUC score
roc.lda_score = roc(response = test_wdbc$V2, predictor = test.lda_probs)  
#roc.lda_score
# 0.9396

## plot the ROC curve
ggroc(roc.lda_score, linetype=1, size = 1) + 
        xlab("FPR") + ylab("TPR") +
        ggtitle("Test ROC curve for LDA")

roc.lda_score
# 0.9396
## a.
## quadratic discriminant analysis 
qda.wdbc = qda(Dig ~ V3 + V4, data = train_wdbc, center = TRUE)
qda.wdbc
qda_table <- cbind(qda.wdbc$prior, qda.wdbc$mean)
colnames(qda_table) <- c("Prior prob of groups", "Group means(radius)", "Group means(texture)")

kable(qda_table)
##------------------
## b.
## fit the model and calculate post prob
train_pred.qda <- predict(qda.wdbc, X_train)$posterior
test_pred.qda <- predict(qda.wdbc, X_test)$posterior

## compute outcome Y based on the post prob on training set
train_outcome.qda <- apply(train_pred.qda, 1, which.max)
## outcome variable = 1 or 2 
## transfer them to B or M
train_outcome.qda <- data.frame(train_outcome.qda)
train_outcome.qda$train_outcome.qda <- 
        ifelse(train_outcome.qda$train_outcome.qda==1, "B", "M")

## compute outcome Y based on the post prob on testing set
test_outcome.qda <- apply(test_pred.qda, 1, which.max)
## outcome variable = 1 or 2 
## transfer them to B or M
test_outcome.qda <- data.frame(test_outcome.qda)
test_outcome.qda$test_outcome.qda <- 
        ifelse(test_outcome.qda$test_outcome.qda==1, "B", "M")

## confusion matrix
train_confusion.qda <- table(Prediction = train_outcome.qda$train_outcome.qda, 
                             Actual = train_wdbc$V2)
test_confusion.qda <- table(Prediction = test_outcome.qda$test_outcome.qda, 
                            Actual = test_wdbc$V2)

## prediction accuracy
train_accuracy.qda <- sum(diag(train_confusion.qda)) / nrow(train_wdbc)
test_accuracy.qda <- sum(diag(test_confusion.qda)) / nrow(test_wdbc)

kable(train_confusion.qda,
      caption = "Confusion table for training set - QDA")

kable(test_confusion.qda,
      caption = "Confusion table for testing set - QDA")

##------------------
## c.
## use the fitted GLM model to predict the outcome probabilities for each observation in the dense set
probs.qda <- predict(qda.wdbc, newdata = dense_set, type = "response")$posterior

# Compute the predicted outcomes using Bayes rule with different probability cutoff values
pred_outcomes_05.qda = ifelse(probs.qda[,1] > probs.qda[,2], "B", "M")
pred_outcomes_25.qda = ifelse(probs.qda[,1] > 0.25, "B", "M")
pred_outcomes_75.qda = ifelse(probs.qda[,1] > 0.75, "B", "M")

## Plot the predicted outcomes for probability cutoff of 0.5
ggplot(data = dense_set, aes(x = V3, y = V4, color = factor(pred_outcomes_05.qda))) +
        geom_point(alpha = 0.2) +
        geom_point(data = train_wdbc, aes(x = V3, y = V4, color = factor(V2))) +
        scale_color_discrete(name = "Prediction") +
        ggtitle("QDA Decision Boundary (Cutoff = 0.5)") +
        xlab("Average radius") + ylab("Average texture")
## Plot the predicted outcomes for probability cutoff of 0.25
ggplot(data = dense_set, aes(x = V3, y = V4, color = factor(pred_outcomes_25.qda))) +
        geom_point(alpha = 0.2) +
        geom_point(data = train_wdbc, aes(x = V3, y = V4, color = factor(V2))) +
        scale_color_discrete(name = "Prediction") +
        ggtitle("QDA Decision Boundary (Cutoff = 0.25)") +
        xlab("Average radius") + ylab("Average texture")
## Plot the predicted outcomes for probability cutoff of 0.75
ggplot(data = dense_set, aes(x = V3, y = V4, color = factor(pred_outcomes_75.qda))) +
        geom_point(alpha = 0.2) +
        geom_point(data = train_wdbc, aes(x = V3, y = V4, color = factor(V2))) +
        scale_color_discrete(name = "Prediction") +
        ggtitle("QDA Decision Boundary (Cutoff = 0.75)") +
        xlab("Average radius") + ylab("Average texture")
##------------------
## d.
test.qda_probs <- predict(qda.wdbc, newdata = test_wdbc, type = "response")$posterior
## calculate AUC score
roc.qda_score = roc(response = test_wdbc$V2, predictor = test.qda_probs[, 2])  
#roc.qda_score
# 0.9361

## plot the ROC curve
ggroc(roc.qda_score, linetype=1, size = 1) + 
        xlab("FPR") + ylab("TPR") +
        ggtitle("Test ROC curve for QDA")

roc.qda_score
# 0.9361
##------------------------------------ KNN
## a.
## knn 
k_values <- c(1, 2, 3, 4, 20)
for (k in k_values) {
        # train set
        knn.train.label <- knn(train = train_wdbc[,c("V3", "V4")], 
                               test = train_wdbc[,c("V3", "V4")], 
                               cl = train_wdbc$V2, k = k)
        
        # confusion table 
        confusion_table_train <- table(knn.train.label, train_wdbc$V2)
        # prediction accuracy
        accuracy_train <- sum(diag(confusion_table_train)) / sum(confusion_table_train)
        
        # test set
        knn.test.label <- knn(train = train_wdbc[,c("V3", "V4")], 
                              test = test_wdbc[,c("V3", "V4")], 
                              cl = train_wdbc$V2, k = k)
        
        # confusion table - test set
        confusion_table_test <- table(knn.test.label, test_wdbc$V2)
        # prediction accuracy
        accuracy_test <- sum(diag(confusion_table_test)) / sum(confusion_table_test)
        
        # Print the results
        cat("For k =", k, ":\n")
        cat("Training set confusion table:\n")
        print(confusion_table_train)
        cat("Training set accuracy:", accuracy_train, "\n")
        cat("Test set confusion table:\n")
        print(confusion_table_test)
        cat("Test set accuracy:", accuracy_test, "\n\n")
}

##------------------
## b.
## k=1
model.knn.1 = knn(train = train_wdbc[,c("V3", "V4")], 
            test = dense_set[,c("V3", "V4")], 
            cl = train_wdbc$V2, 
            k = 1)

## Plot 
ggplot(data = dense_set, aes(x = V3, y = V4, color = factor(model.knn.1))) +
        geom_point(alpha = 0.2) +
        geom_point(data = train_wdbc, aes(x = V3, y = V4, color = factor(V2))) +
        scale_color_discrete(name = "Prediction") +
        ggtitle("KNN Decision Boundary (k=1)") +
        xlab("Average radius") + ylab("Average texture")

## k=2
model.knn.2 = knn(train = train_wdbc[,c("V3", "V4")], 
                  test = dense_set[,c("V3", "V4")], 
                  cl = train_wdbc$V2, 
                  k = 2)

## Plot 
ggplot(data = dense_set, aes(x = V3, y = V4, color = factor(model.knn.2))) +
        geom_point(alpha = 0.2) +
        geom_point(data = train_wdbc, aes(x = V3, y = V4, color = factor(V2))) +
        scale_color_discrete(name = "Prediction") +
        ggtitle("KNN Decision Boundary (k=2)") +
        xlab("Average radius") + ylab("Average texture")

## k=3
model.knn.3 = knn(train = train_wdbc[,c("V3", "V4")], 
                  test = dense_set[,c("V3", "V4")], 
                  cl = train_wdbc$V2, 
                  k = 3)

## Plot 
ggplot(data = dense_set, aes(x = V3, y = V4, color = factor(model.knn.3))) +
        geom_point(alpha = 0.2) +
        geom_point(data = train_wdbc, aes(x = V3, y = V4, color = factor(V2))) +
        scale_color_discrete(name = "Prediction") +
        ggtitle("KNN Decision Boundary (k=3)") +
        xlab("Average radius") + ylab("Average texture")

## k=4
model.knn.4 = knn(train = train_wdbc[,c("V3", "V4")], 
                  test = dense_set[,c("V3", "V4")], 
                  cl = train_wdbc$V2, 
                  k = 4)

## Plot 
ggplot(data = dense_set, aes(x = V3, y = V4, color = factor(model.knn.4))) +
        geom_point(alpha = 0.2) +
        geom_point(data = train_wdbc, aes(x = V3, y = V4, color = factor(V2))) +
        scale_color_discrete(name = "Prediction") +
        ggtitle("KNN Decision Boundary (k=4)") +
        xlab("Average radius") + ylab("Average texture")

## k=20
model.knn.20 = knn(train = train_wdbc[,c("V3", "V4")], 
                  test = dense_set[,c("V3", "V4")], 
                  cl = train_wdbc$V2, 
                  k = 20)

## Plot 
ggplot(data = dense_set, aes(x = V3, y = V4, color = factor(model.knn.20))) +
        geom_point(alpha = 0.2) +
        geom_point(data = train_wdbc, aes(x = V3, y = V4, color = factor(V2))) +
        scale_color_discrete(name = "Prediction") +
        ggtitle("KNN Decision Boundary (k=20)") +
        xlab("Average radius") + ylab("Average texture")

##------------------
## c.
## plot the prediction accuracy 
train_accuracy = numeric(20)
test_accuracy = numeric(20)

for (i in 1:20) {
        knn.label <- knn(train = train_wdbc[,c("V3","V4")], 
                        test = train_wdbc[,c("V3","V4")], 
                        cl = train_wdbc$V2, k = i)
        train_accuracy[i] <- mean(knn.label == train_wdbc$V2)
        test_accuracy[i] <- mean(knn.label == test_wdbc$V2)
}

for (i in 1:20) {
        knn.label <- knn(train = train_wdbc[,c("V3", "V4")], 
                         test = test_wdbc[,c("V3", "V4")], 
                         cl = train_wdbc$V2, k = i)
        test_accuracy[i] <- mean(knn.label == test_wdbc$V2)
}

## train set
plot(1:20, train_accuracy, type="l", col="blue",
     xlab="k", ylab="accuracy",
     main="Prediction accuracy for different value of k (train set)")

## test set
plot(1:20, test_accuracy, type="l", col="red",
     xlab="k", ylab="accuracy",
     main="Prediction accuracy for different value of k (test set)")
LS0tCnRpdGxlOiAiQmluYXJ5IENsYXNzaWZpY2F0aW9uIG9uIHRoZSBCcmVhc3QgQ2FuY2VyIERpYWdub3N0aWMgRGF0YSIKc3VidGl0bGU6ICJTaW1wbGUgTG9naXN0aWMgUmVncmVzc2lvbiwgTGluZWFyL1F1YWRyYXRpYyBkaXNjcmltaW5hbnQgQW5hbHlzaXMgTW9kZWxzICYgS05OIgphdXRob3I6ICJNYWtheWxhIFRhbmciCmRhdGU6ICcyMDIzIEZlYicKYWJzdHJhY3Q6IAogIEluIHRoaXMgYW5hbHlzaXMsIHdlIHBlcmZvcm0gYmluYXJ5IGNsYXNzaWZpY2F0aW9uIG9uIHRoZSBCcmVhc3QgQ2FuY2VyIFdpc2NvbnNpbiAoRGlhZ25vc3RpYykgRGF0YSBTZXQgaW4gdGhlIGNzdiBmaWxlIHdkYmMuZGF0YS4gVGhlIGRhdGFzZXQgZGVzY3JpYmVzIGNoYXJhY3RlcmlzdGljcyBvZiB0aGUgY2VsbCBudWNsZWkgcHJlc2VudCBpbiBuIChzYW1wbGUgc2l6ZSkgaW1hZ2VzLiBFYWNoIGltYWdlIGhhcyBtdWx0aXBsZSBhdHRyaWJ1dGVzLCB3aGljaCBhcmUgZGVzY3JpYmVkIGluIGRldGFpbCBpbiB3ZGJjLm5hbWVzLiBXZSBwcmVkaWN0IHRoZSBhdHRyaWJ1dGUgaW4gY29sdW1uIDIsIHdoaWNoIHdlIGRlbm90ZSBieSBZLCByZXByZXNlbnRpbmcgdGhlIGRpYWdub3NpcyAoTSA9IG1hbGlnbmFudCwgQiA9IGJlbmlnbikuIEhlcmUsIHdlIGZvY3VzIG9uIHRoZSBhdHRyaWJ1dGVzIGluIGNvbHVtbnMgezI6RGlhZ25vc2lzLCAzOkF2ZXJhZ2UgcmFkaXVzIG9mIHRoZSBjZWxsIG51Y2xlaSBpbiBlYWNoIGltYWdlLCA0OkF2ZXJhZ2UgdGV4dHVyZSBvZiB0aGUgY2VsbCBudWNsZWkgaW4gZWFjaCBpbWFnZX0uIAogIFNwZWNpZmljYWxseSwgb3VyIGFpbSB3aWxsIGJlIHByZWRpY3RpbmcgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZSBZIChEaWFnbm9zaXMg4oCTIGNvbHVtbiAyKSwgZnJvbSB0aGUgcXVhbnRpdGF0aXZlIGF0dHJpYnV0ZXMgWDEgKEF2ZXJhZ2UgcmFkaXVzIOKAkyBjb2x1bW4gMykgYW5kIFgyIChBdmVyYWdlIHRleHR1cmUg4oCTIGNvbHVtbiA0KS4gCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgICAgIAotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmtuaXRyOjpvcHRzX2NodW5rJHNldCh3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSkgCmBgYAoKYGBge3IgbGlicmFyeSwgaW5jbHVkZT1GQUxTRX0KIyMgbG9hZCB0aGUgbGlicmFyaWVzCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShNQVNTKSAgIyBJbXBsZW1lbnRzIExEQSAmIFFEQQpsaWJyYXJ5KGNhcmV0KSAjIGNvbmZ1c2lvbk1hdHJpeApsaWJyYXJ5KHBST0MpICAjIFJPQyAmIEFVQwpsaWJyYXJ5KGNsYXNzKSAjIEltcGxlbWVudHMgS05OCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoR0dhbGx5KQoKIyMtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMgbG9hZCB0aGUgZGF0YXNldAp3ZGJjIDwtIHJlYWQuY3N2KCJ3ZGJjLmRhdGEiLCBoZWFkZXIgPSBGQUxTRSkKaGVhZCh3ZGJjKQojIyBzZWxlY3QgY29sdW1uIHsyLDMsNH0Kd2RiYyA8LSB3ZGJjICU+JSBzZWxlY3QoYygiVjIiLCAiVjMiLCAiVjQiKSkKCmBgYAoKCiMjIDEuRGF0YSBleHBsb3JhdGlvbiArIExvZ2lzdGljIFJlZ3Jlc3Npb24KCioqYS5EZXNjcmliZSB0aGUgZGF0YTogc2FtcGxlIHNpemUgbiwgbnVtYmVyIG9mIHByZWRpY3RvcnMgcCwgYW5kIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgaW4gZWFjaCBjbGFzcy4qKgoKKiBuKHNhbXBsZSBzaXplKTogNTY5CiogbnVtYmVyIG9mIHByZWRpY3RvcnM6IDIKKiBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIGluIGVhY2ggY2xhc3M6IE09MjEyOyBCPTM1NyAKCmBgYHtyIDFhICxyZXN1bHRzPSdoaWRlJ30KIyMtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gCiMgYS4gCndkYmMgJT4lIHN1bW1hcmlzZShuPW4oKSkKd2RiYyAlPiUgZ3JvdXBfYnkoVjIpICU+JSBzdW1tYXJpc2Uobj1uKCksICkKZ2xpbXBzZSh3ZGJjKQpgYGAKCioqYi5EaXZpZGUgdGhlIGRhdGEgaW50byBhIHRyYWluaW5nIHNldCBvZiA0MDAgb2JzZXJ2YXRpb25zLCBhbmQgYSB0ZXN0IHNldDsgZnJvbSBub3cgb24sIHVubGVzcyBzcGVjaWZpZWQsIHdvcmsgb25seSBvbiB0aGUgdHJhaW5pbmcgc2V0LioqCgpgYGB7ciAxYiwgcmVzdWx0cyA9ICJoaWRlIn0KIyMtLS0tLS0tLS0tLS0tLS0tLS0KIyMgYi4KIyMgRGl2aWRlIHRoZSBkYXRhIGludG8gYSB0cmFpbmluZyBzZXQgb2YgNDAwIG9icwpzZXQuc2VlZCgxMDEpCnRyYWluX2lkX3dkYmMgPSBzYW1wbGUobnJvdyh3ZGJjKSwgNDAwKQp0cmFpbl93ZGJjID0gd2RiY1t0cmFpbl9pZF93ZGJjLCBdCnRlc3Rfd2RiYyAgPSB3ZGJjWy10cmFpbl9pZF93ZGJjLCBdCmBgYAoKKipjLk1ha2UgYSBzY2F0dGVycGxvdCBkaXNwbGF5aW5nIFkgKGNvbG9yIG9yIHNoYXBlIGVuY29kZWQpIGFuZCB0aGUgcHJlZGljLSB0b3JzIFgxLCBYMiAob24gdGhlIHgtIGFuZCB5LWF4aXMpLiBCYXNlZCBvbiB0aGlzIHNjYXR0ZXJwbG90LCBkbyB5b3UgdGhpbmsgaXQgd2lsbCBiZSBwb3NzaWJsZSB0byBhY2N1cmF0ZWx5IHByZWRpY3QgdGhlIG91dGNvbWUgZnJvbSB0aGUgcHJlZGljdG9ycz8gTW90aXZhdGUgeW91ciBhbnN3ZXIuKioKCkJhc2VkIG9uIHRoZSBwbG90LCB3ZSBjb25jbHVkZSB0aGF0IHRoZSBhdmVyYWdlIHJhZGl1cyBpcyBhIGJldHRlciBwcmVkaWN0b3IgZm9yIHRoZSBkaWFnbm9zaXMgYXMgdGhlIGJsdWUgcG9pbnRzIChkaWFnbm9zaXMgd2l0aCBtYWxpZ25hbnQpIHRlbmQgdG8gaGF2ZSBoaWdoZXIgdmFsdWVzIGFuZCB0aGUgcmVkIHBvaW50cyAoZGlhZ25vc2lzIHdpdGggYmVuaWduKSB0ZW5kIHRvIGhhdmUgbG93ZXIgdmFsdWVzLCB3aGlsZSB0aGUgYXZlcmFnZSB0ZXh0dXJlIGRvZXMgbm90IHNlZW0gdG8gYmUgYSBzdHJvbmcgcHJlZGljdG9yIGFzIHRoZSByZWQgcG9pbnRzIGFuZCB0aGUgYmx1ZSBwb2ludHMgYXJlIHNwcmVhZCBldmVubHkuCgpgYGB7ciAxYywgZWNobz1GQUxTRSwgb3V0LndpZHRoID0gIjcwJSIsIGZpZy5hbGlnbiA9ICdjZW50ZXInfQojIy0tLS0tLS0tLS0tLS0tLS0tLQojIyBjLgojIyBzY2F0dGVycGxvdAp0cmFpbl93ZGJjICU+JSBnZ3Bsb3QoYWVzKHggPSBWMywgeSA9IFY0LCBjb2xvciA9IGZhY3RvcihWMikpKSArIAogICAgICAgIGdlb21fcG9pbnQoKSArIAogICAgICAgIHhsYWIoIkF2ZXJhZ2UgcmFkaXVzIG9mIHRoZSBjZWxsIG51Y2xlaSIpICsgeWxhYigiQXZlcmFnZSB0ZXh0dXJlIG9mIHRoZSBjZWxsIG51Y2xlaSIpICsgCiAgICAgICAgZ2d0aXRsZSgiU2NhdHRlcnBsb3Qgb2YgcmFkaXVzIGFuZCB0ZXh1cmUgd2l0aCBjb2xvciBlbmNvZGVkIGRpYWdub3NpcyIpCgpgYGAKCioqZC5GaXQgYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIHRvIHByZWRpY3QgWSBhbmQgbWFrZSBhIHRhYmxlLCBkaXNwbGF5aW5nIHRoZSBjb2VmZmljaWVudCBlc3RpbWF0ZXMsIHN0YW5kYXJkIGVycm9ycywgYW5kIHAtdmFsdWVzICh1c2UgY29tbWFuZCBzdW1tYXJ5KS4gR2l2ZSBhbiBpbnRlcnByZXRhdGlvbiBvZiB0aGUgdmFsdWVzIG9mIHRoZSBjb2VmZmljaWVudCBlc3RpbWF0ZXMuKioKCiogVGhlIGVzdGltYXRlZCBleHBlY3RlZCBsb2ctb2RkcyBvZiBkaWFnbm9zaXMgd2l0aCBtYWxpZ25hbnQgZm9yIGEgcG9wdWxhdGlvbiB3aXRoIGF2ZXJhZ2UgcmFkaXVtID0gMCBhbmQgYXZlcmFnZSB0ZXh0dXJlID0gMCBpcyAtMjEuMzgxCgoqIFRoZSBsb2ctb2RkcyBvZiBkaWFnbm9zaXMgd2l0aCBtYWxpZ25hbnQgaXMgZXN0aW1hdGVkIHRvIGJlIDEuMTIzIHRpbWVzIGdyZWF0ZXIgZm9yIGEgcG9wdWxhdGlvbiB3aXRoIG9uZSBtb3JlIHVuaXQgaW5jcmVhc2UgaW4gdGhlIGF2ZXJhZ2UgcmFkaXVzIG9mIHRoZSBjZWxsIG51Y2xlaSwgY29tcGFyZWQgdG8gYSBwb3B1bGF0aW9uIG9mIHRoZSBzYW1lIGF2ZXJhZ2UgdGV4dHVyZSBvZiB0aGUgY2VsbCBudWNsZWkuIAoKKiBUaGUgbG9nLW9kZHMgb2YgdGhlIGRpYWdub3NpcyB3aXRoIG1hbGlnbmFudCBpcyBlc3RpbWF0ZWQgdG8gYmUgMC4yNDcgdGltZXMgZ3JlYXRlciBmb3IgYSBwb3B1bGF0aW9uIHdpdGggb25lIG1vcmUgdW5pdCBpbmNyZWFzZSBpbiB0aGUgYXZlcmFnZSB0ZXh0dXJlIG9mIHRoZSBjZWxsIG51Y2xlaSwgY29tcGFyZWQgdG8gYSBwb3B1bGF0aW9uIG9mIHRoZSBzYW1lIGF2ZXJhZ2UgcmFkaXVzIG9mIHRoZSBjZWxsIG51Y2xlaS4KCmBgYHtyIDFkLCByZXN1bHRzPSdoaWRlJ30KIyMtLS0tLS0tLS0tLS0tLS0tLS0KIyMgbG9naXN0aWMgbW9kZWwKIyMgdHJhbnNmZXIgTSB2YXJpYWJsZSBmcm9tIGNoYXJhY3RlciB0byBudW1lcmljICgwLDEpCnRyYWluX3dkYmMkRGlnIDwtIGlmZWxzZSAodHJhaW5fd2RiYyRWMj09Ik0iLCAxLCAwKQoKZ2xtLndkYmMgPSBnbG0oRGlnIH4gVjMgKyBWNCwgZmFtaWx5ID0gYmlub21pYWwobGluayA9ICJsb2dpdCIpLCBkYXRhID0gdHJhaW5fd2RiYykKc3VtbWFyeShnbG0ud2RiYykKCmBgYAoKYGBge3IgMWQgdGFibGV9CmthYmxlKHN1bW1hcnkoZ2xtLndkYmMpJGNvZWZmaWNpZW50cykKYGBgCgoqKmUuVXNlIHRoZSBjb2VmZmljaWVudCBlc3RpbWF0ZXMgdG8gbWFudWFsbHkgY2FsY3VsYXRlIHRoZSBwcmVkaWN0ZWQgcHJvYmFiaWwtIGl0eSBQKFkgPSBNIHwgKFgxLFgyKSA9ICgxMCwxMikpIHdyaXRpbmcgZXhwbGljaXRseSBldmVyeSBzdGVwLiBDb21wYXJlIHlvdXIgcmVzdWx0cyB3aXRoIHRoZSBwcmVkaWN0aW9uIGNvbXB1dGVkIHdpdGggcHJlZGljdC4qKgoKKiBtYW51YWxseSBjYWxjdWxhdGU6ICRwcmVkaWN0aW9uID0gLTIxLjM4MSArIDEuMTIzKjEwICsgMC4yNDcqMTIgPSAtNy4xNzg5MDEkLCAkcHJvYmFiaWxpdHkgPSAxLygxK2V4cCgtNy4xNzg5MDEpID0gMC4wMDA3NiQKCiogcHJlZGljdChnbG0ud2RiYywgZGF0YS5mcmFtZShWMyA9IDEwLCBWNCA9IDEyKSwgdHlwZSA9ICJyZXNwb25zZSIpID0gMC4wMDA3NgoKKiBUaGVyZWZvcmUsIHRoZSB0d28gcmVzdWx0cyBhcmUgdGhlIHNhbWUuIAoKYGBge3IgMWUsIHJlc3VsdHM9ImhpZGUifQojIy0tLS0tLS0tLS0tLS0tLS0tLQojIyBlLgojIyBleHRyYWN0IHRoZSBjb2VmZmljaWVudHMKYmV0YV8wIDwtIGNvZWYoZ2xtLndkYmMpWzFdCmJldGFfMSA8LSBjb2VmKGdsbS53ZGJjKVsyXQpiZXRhXzIgPC0gY29lZihnbG0ud2RiYylbM10KCiMjIGNhbGN1bGF0ZSB0aGUgbGluZWFyIHByZWRpY3RvcgpwcmVkIDwtIGJldGFfMCArIGJldGFfMSoxMCArIGJldGFfMioxMgoKIyMgY2FsY3VsYXRlIHRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdHkKcHJvYiA8LSAxIC8gKDEgKyBleHAoLXByZWQpKQpwcm9iCiMgMC4wMDA3NjE5MjQ2CgojIGNvbXBhcmUgd2l0aCB0aGUgcHJlZGljdGlvbiBjb21wdXRlZCB1c2luZyBwcmVkaWN0CnByZWRpY3QoZ2xtLndkYmMsIGRhdGEuZnJhbWUoVjMgPSAxMCwgVjQgPSAxMiksIHR5cGUgPSAicmVzcG9uc2UiKQojIDAuMDAwNzYxOTI0NgoKIyBUaGUgdHdvIHZhbHVlcyBhcmUgdGhlIHNhbWUuIApgYGAKCioqZi5Vc2UgdGhlIGdsbSBwcmV2aW91c2x5IGZpdHRlZCBhbmQgdGhlIEJheWVzIHJ1bGUgdG8gY29tcHV0ZSB0aGUgcHJlLSBkaWN0ZWQgb3V0Y29tZSBZy4YgZnJvbSB0aGUgYXNzb2NpYXRlZCBwcm9iYWJpbGl0eSBlc3RpbWF0ZXMgKGNvbXB1dGVkIHdpdGggcHJlZGljdCkgYm90aCBvbiB0aGUgdHJhaW5pbmcgYW5kIHRoZSB0ZXN0IHNldC4gVGhlbiBjb21wdXRlIHRoZSBjb25mdXNpb24gdGFibGUgYW5kIHByZWRpY3Rpb24gYWNjdXJhY3kgKHJhdGUgb2YgY29ycmVjdGx5IGNsYXNzaWZpZWQgb2JzZXItIHZhdGlvbnMpIGJvdGggb24gdGhlIHRyYWluaW5nIGFuZCB0ZXN0IHNldC4gQ29tbWVudCBvbiB0aGUgcmVzdWx0cy4qKgoKKiB0cmFpbmluZyBzZXQ6IGFjY3VyYWN5ID0gMC45MDUKCiogdGVzdGluZyBzZXQ6IGFjY3VyYWN5ID0gMC44ODIKClRoZSBtb2RlbCBhY2N1cmFjeSBmb3IgdHJhaW5pbmcgZGF0YSBhbmQgdGhlIG9uZSBmb3IgdGVzdGluZyBkYXRhIGFyZSBzaW1pbGFyLiBJdCBpbmRpY2F0ZXMgdGhhdCB0aGUgZ29vZCBtb2RlbCBwZXJmb3JtYW5jZS4gCgpgYGB7ciAxZiwgcmVzdWx0cz0naGlkZSd9CiMjLS0tLS0tLS0tLS0tLS0tLS0tCiMjIGYuCiMjIENvbXB1dGUgdGhlIHByZWRpY3RlZCBwcm9iYWJpbGl0aWVzIHVzaW5nIHRoZSBwcmVkaWN0IGZ1bmN0aW9uCnRyYWluX3Byb2JzIDwtIHByZWRpY3QoZ2xtLndkYmMsIG5ld2RhdGEgPSB0cmFpbl93ZGJjLCB0eXBlID0gInJlc3BvbnNlIikKdGVzdF9wcm9icyA8LSBwcmVkaWN0KGdsbS53ZGJjLCBuZXdkYXRhID0gdGVzdF93ZGJjLCB0eXBlID0gInJlc3BvbnNlIikKCiMjIFVzZSB0aGUgQmF5ZXMgcnVsZSB0byBjb21wdXRlIHRoZSBwcmVkaWN0ZWQgb3V0Y29tZSAKdHJhaW5fcHJlZGljdGlvbiA8LSBpZmVsc2UodHJhaW5fcHJvYnMgPiAwLjUsICJNIiwgIkIiKQp0ZXN0X3ByZWRpY3Rpb24gPC0gaWZlbHNlKHRlc3RfcHJvYnMgPiAwLjUsICJNIiwgIkIiKQoKIyMgQ3JlYXRlIGNvbmZ1c2lvbiB0YWJsZXMgb24gdGhlIHRyYWluaW5nIGFuZCB0ZXN0IHNldHMKdHJhaW5fY29uZnVzaW9uX3RhYmxlIDwtIHRhYmxlKFByZWRpY3Rpb24gPSB0cmFpbl9wcmVkaWN0aW9uLCBBY3R1YWwgPSB0cmFpbl93ZGJjJFYyKQp0ZXN0X2NvbmZ1c2lvbl90YWJsZSA8LSB0YWJsZShQcmVkaWN0aW9uID0gdGVzdF9wcmVkaWN0aW9uLCBBY3R1YWwgPSB0ZXN0X3dkYmMkVjIpCgojIyBDYWxjdWxhdGUgcHJlZGljdGlvbiBhY2N1cmFjeSBvbiB0aGUgdHJhaW5pbmcgYW5kIHRlc3Qgc2V0cwp0cmFpbl9hY2N1cmFjeSA8LSBzdW0oZGlhZyh0cmFpbl9jb25mdXNpb25fdGFibGUpKSAvIG5yb3codHJhaW5fd2RiYykKdGVzdF9hY2N1cmFjeSA8LSBzdW0oZGlhZyh0ZXN0X2NvbmZ1c2lvbl90YWJsZSkpIC8gbnJvdyh0ZXN0X3dkYmMpCgpgYGAKCmBgYHtyIDFmIHRhYmxlfQprYWJsZSh0cmFpbl9jb25mdXNpb25fdGFibGUsCiAgICAgIGNhcHRpb24gPSAiQ29uZnVzaW9uIFRhYmxlIGZvciB0cmFpbmluZyBzZXQiKQpgYGAKCmBgYHtyIDJmIHRhYmxlfQprYWJsZSh0ZXN0X2NvbmZ1c2lvbl90YWJsZSwgCiAgICAgIGNhcHRpb24gPSAiQ29uZnVzaW9uIFRhYmxlIGZvciB0ZXN0aW5nIHNldCIpCmBgYAoKKipnLlBsb3QgYW4gaW1hZ2Ugb2YgdGhlIGRlY2lzaW9uIGJvdW5kYXJ5IGFzIGZvbGxvd3M6MS5HZW5lcmF0ZSBhIGRlbnNlIHNldCAoZS5nLiAxMDAwMCBvYnNlcnZhdGlvbnMpIG9mIHBvc3NpYmxlIHZhbHVlcyBmb3IgdGhlIHByZWRpY3RvcnMgKFgxLFgyKSB3aXRoaW4gcmVhc29uYWJsZSByYW5nZXM7ICh0aGUgY29tbWFuZCBleHBhbmQuZ3JpZCBtaWdodCBjb21lIGluIGhhbmR5KTsgMi5Vc2UgdGhlIGdsbSBtb2RlbCBwcmV2aW91c2x5IGZpdHRlZCB0byBwcmVkaWN0IHRoZSBvdXRjb21lIHByb2JhYmlsLSBpdGllcyBmb3IgZXZlcnkgb2JzZXJ2YXRpb24geW91IGhhdmUgZ2VuZXJhdGVkIGFuZCB1c2UgQmF5ZXMgcnVsZSB0byBjb21wdXRlIHRoZSBwcmVkaWN0ZWQgb3V0Y29tZXM7IDMuUGxvdCBwcmVkaWN0ZWQgb3V0Y29tZXMgKGNvbG9yIGNvZGVkKSBhbmQgYXNzb2NpYXRlZCBwcmVkaWN0b3JzIGluIGEgMkQgc2NhdHRlciBwbG90IHRvZ2V0aGVyIHdpdGggdGhlIHRyYWluaW5nIHNldC5HZW5lcmF0ZSB0aGUgc2FtZSBwbG90IGZvciBwcm9iYWJpbGl0eSBjdXRvZmYgdmFsdWVzIG9mIDAuMjUgYW5kIDAuNzUuIENvbW1lbnQgb24gdGhlIHJlc3VsdHMuKioKClRoZSBpbmNyZWFzZSBpbiB0aGUgcmVkIHJlZ2lvbiAocHJlZGljdGVkIGFzIGJlbmlnbiB0dW1vcikgYXMgdGhlIGN1dG9mZiB2YWx1ZSBpbmNyZWFzZXMuIFdoZW4gdGhlIGN1dG9mZiB2YWx1ZSBpcyBsb3csIHRoZSBtb2RlbCB3b3VsZCBiZSBtb3JlIGNvbnNlcnZhdGl2ZSBpbiBjbGFzc2lmeWluZyBhbiBvYnNlcnZhdGlvbiBhcyBiZW5pZ24sIHdoaWxlIGFzIHRoZSBjdXRvZmYgdmFsdWUgaW5jcmVhc2VzLCB0aGUgbW9kZWwgYmVjb21lcyBtb3JlIGNvbmZpZGVudCBpbiBjbGFzc2lmeWluZyBhbiBvYnNlcnZhdGlvbiBhcyBiZW5pZ24uIFRoZXJlZm9yZSwgdGhlIHJlZCByZWdpb24gd291bGQgaW5jcmVhc2Ugd2l0aCBoaWdoZXIgY3V0b2ZmIHZhbHVlLiAKCmBgYHtyIDFnLCByZXN1bHRzPSdoaWRlJ30KIyMgZy4KIyMgc3BlY2lmeSByZWFzb25hYmxlIHJhbmdlcyBmb3IgWDEgYW5kIFgyCngxX3JhbmdlIDwtIGMobWluKHRyYWluX3dkYmMkVjMpLCBtYXgodHJhaW5fd2RiYyRWMykpCngyX3JhbmdlIDwtIGMobWluKHRyYWluX3dkYmMkVjQpLCBtYXgodHJhaW5fd2RiYyRWNCkpCgojIyBjcmVhdGUgYSBkZW5zZSBzZXQgb2YgcG9zc2libGUgdmFsdWVzIGZvciBYMSBhbmQgWDIgdXNpbmcgZXhwYW5kLmdyaWQKZGVuc2Vfc2V0IDwtIGV4cGFuZC5ncmlkKFYzID0gc2VxKGZyb20gPSB4MV9yYW5nZVsxXSwgdG8gPSB4MV9yYW5nZVsyXSwgbGVuZ3RoLm91dCA9IDEwMCksCiAgICAgICAgICAgICAgICAgICAgICAgICBWNCA9IHNlcShmcm9tID0geDJfcmFuZ2VbMV0sIHRvID0geDJfcmFuZ2VbMl0sIGxlbmd0aC5vdXQgPSAxMDApKQoKIyMgdXNlIHRoZSBmaXR0ZWQgR0xNIG1vZGVsIHRvIHByZWRpY3QgdGhlIG91dGNvbWUgcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBvYnNlcnZhdGlvbiBpbiB0aGUgZGVuc2Ugc2V0CmRlbnNlX3NldCRwcm9icyA8LSBwcmVkaWN0KGdsbS53ZGJjLCBuZXdkYXRhID0gZGVuc2Vfc2V0LCB0eXBlID0gInJlc3BvbnNlIikKCiMjIGNvbXB1dGUgdGhlIHByZWRpY3RlZCBvdXRjb21lcyB1c2luZyBCYXllcyBydWxlCmRlbnNlX3NldCRwcmVkaWN0aW9uIDwtIGlmZWxzZShkZW5zZV9zZXQkcHJvYnMgPiAwLjUsICJNIiwgIkIiKQpgYGAKCmBgYHtyIDFnX3Bsb3QxLCBlY2hvPUZBTFNFLCBvdXQud2lkdGggPSAiNzAlIiwgZmlnLmFsaWduID0gJ2NlbnRlcid9CiMjIFBsb3QgdGhlIHByZWRpY3RlZCBvdXRjb21lcyBmb3IgcHJvYmFiaWxpdHkgY3V0b2ZmIG9mIDAuNQpnZ3Bsb3QoZGF0YSA9IGRlbnNlX3NldCwgYWVzKHggPSBWMywgeSA9IFY0LCBjb2xvciA9IGZhY3RvcihwcmVkaWN0aW9uKSkpICsKICAgICAgICBnZW9tX3BvaW50KGFscGhhID0gMC4yKSArCiAgICAgICAgZ2VvbV9wb2ludChkYXRhID0gdHJhaW5fd2RiYywgYWVzKHggPSBWMywgeSA9IFY0LCBjb2xvciA9IGZhY3RvcihWMikpKSArCiAgICAgICAgc2NhbGVfY29sb3JfZGlzY3JldGUobmFtZSA9ICJQcmVkaWN0aW9uIikgKwogICAgICAgIGdndGl0bGUoIkxvZ2lzdGljIFJlZ3Jlc3Npb24gRGVjaXNpb24gQm91bmRhcnkgKEN1dG9mZiA9IDAuNSkiKSArCiAgICAgICAgeGxhYigiQXZlcmFnZSByYWRpdXMiKSArIHlsYWIoIkF2ZXJhZ2UgdGV4dHVyZSIpCmBgYAoKYGBge3IgMWdfcGxvdDIsIGVjaG89RkFMU0UsIG91dC53aWR0aCA9ICI3MCUiLCBmaWcuYWxpZ24gPSAnY2VudGVyJ30KZGVuc2Vfc2V0JHByZWRpY3Rpb25fMjUgPC0gaWZlbHNlKGRlbnNlX3NldCRwcm9icyA+IDAuMjUsICJNIiwgIkIiKQpnZ3Bsb3QoZGF0YSA9IGRlbnNlX3NldCwgYWVzKHggPSBWMywgeSA9IFY0LCBjb2xvciA9IGZhY3RvcihwcmVkaWN0aW9uXzI1KSkpICsKICAgICAgICBnZW9tX3BvaW50KGFscGhhID0gMC4yKSArCiAgICAgICAgZ2VvbV9wb2ludChkYXRhID0gdHJhaW5fd2RiYywgYWVzKHggPSBWMywgeSA9IFY0LCBjb2xvciA9IGZhY3RvcihWMikpKSArCiAgICAgICAgc2NhbGVfY29sb3JfZGlzY3JldGUobmFtZSA9ICJQcmVkaWN0aW9uIikgKwogICAgICAgIGdndGl0bGUoIkxvZ2lzdGljIFJlZ3Jlc3Npb24gRGVjaXNpb24gQm91bmRhcnkgKEN1dG9mZiA9IDAuMjUpIikgKwogICAgICAgIHhsYWIoIkF2ZXJhZ2UgcmFkaXVzIikgKyB5bGFiKCJBdmVyYWdlIHRleHR1cmUiKQpgYGAKCmBgYHtyIDFnX3Bsb3QzLCBlY2hvPUZBTFNFLCBvdXQud2lkdGggPSAiNzAlIiwgZmlnLmFsaWduID0gJ2NlbnRlcid9CmRlbnNlX3NldCRwcmVkaWN0aW9uXzc1IDwtIGlmZWxzZShkZW5zZV9zZXQkcHJvYnMgPiAwLjc1LCAiTSIsICJCIikKZ2dwbG90KGRhdGEgPSBkZW5zZV9zZXQsIGFlcyh4ID0gVjMsIHkgPSBWNCwgY29sb3IgPSBmYWN0b3IocHJlZGljdGlvbl83NSkpKSArCiAgICAgICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMikgKwogICAgICAgIGdlb21fcG9pbnQoZGF0YSA9IHRyYWluX3dkYmMsIGFlcyh4ID0gVjMsIHkgPSBWNCwgY29sb3IgPSBmYWN0b3IoVjIpKSkgKwogICAgICAgIHNjYWxlX2NvbG9yX2Rpc2NyZXRlKG5hbWUgPSAiUHJlZGljdGlvbiIpICsKICAgICAgICBnZ3RpdGxlKCJMb2dpc3RpYyBSZWdyZXNzaW9uIERlY2lzaW9uIEJvdW5kYXJ5IChDdXRvZmYgPSAwLjc1KSIpICsKICAgICAgICB4bGFiKCJBdmVyYWdlIHJhZGl1cyIpICsgeWxhYigiQXZlcmFnZSB0ZXh0dXJlIikKYGBgCgoqKmguUGxvdCB0aGUgUk9DIGN1cnZlLCBjb21wdXRlZCBvbiB0aGUgdGVzdCBzZXQuKioKCmBgYHtyIDFoLCBlY2hvID0gRkFMU0UsIG91dC53aWR0aCA9ICI2MCUiLCBmaWcuYWxpZ24gPSAnY2VudGVyJ30KIyMtLS0tLS0tLS0tLS0tLS0tLS0KIyMgaC4KdGVzdF9wcm9icyA8LSBwcmVkaWN0KGdsbS53ZGJjLCBuZXdkYXRhID0gdGVzdF93ZGJjLCB0eXBlID0gInJlc3BvbnNlIikKIyMgY2FsY3VsYXRlIEFVQyBzY29yZQpyb2Nfc2NvcmUgPSByb2MocmVzcG9uc2UgPSB0ZXN0X3dkYmMkVjIsIHByZWRpY3RvciA9IHRlc3RfcHJvYnMpICAKI3JvY19zY29yZQoKIyMgcGxvdCB0aGUgUk9DIGN1cnZlCmdncm9jKHJvY19zY29yZSwgbGluZXR5cGU9MSwgc2l6ZSA9IDEpICsgCiAgICAgICAgeGxhYigiRlBSIikgKyB5bGFiKCJUUFIiKSArCiAgICAgICAgZ2d0aXRsZSgiVGVzdCBST0MgY3VydmUgZm9yIHRoZSBMb2dpc3RpYyBSZWdyZXNzaW9uIE1vZGVsIikKCmBgYAoKKippLkNvbXB1dGUgYW4gZXN0aW1hdGUgb2YgdGhlIEFyZWEgdW5kZXIgdGhlIFJPQyBDdXJ2ZSAoQVVDKS4qKgoKVGhlIEFVQyBzY29yZSBpcyAwLjkzOTguCgojIyAyLkxpbmVhciBEaXNjcmltaW5hbnQgQW5hbHlzaXMgTW9kZWwKCioqYS5Ob3cgZml0IGEgbGluZWFyIGRpc2NyaW1pbmFudCBhbmFseXNpcyBtb2RlbCB0byB0aGUgdHJhaW5pbmcgc2V0IHlvdSBjcmVhdGVkIGluIEV4ZXJjaXNlIDEuIE1ha2UgYSB0YWJsZSBkaXNwbGF5aW5nIHRoZSBlc3RpbWF0ZWQg4oCYUHJpb3IgcHJvYmFiaWxpdGllcyBvZiBncm91cHPigJkgYW5kIOKAmEdyb3VwIG1lYW5z4oCZLiBEZXNjcmliZSBpbiB3b3JkcyB0aGUgbWVhbmluZyBvZiB0aGVzZSBlc3RpbWF0ZXMgYW5kIGhvdyB0aGV5IGFyZSByZWxhdGVkIHRvIHRoZSBwb3N0ZXJpb3IgcHJvYmFiaWxpdGllcy4qKgoKVGhlIGVzdGltYXRlZCBncm91cCBtZWFucyBhcmUgdGhlIGF2ZXJhZ2Ugb2YgdGhlIHByZWRpY3RvciB2YXJpYWJsZXMgKHJhZGl1cyBvciB0ZXh0dXJlKSBmb3IgZWFjaCBncm91cCAobWFsaWduYW50IG9yIGJlbmlnbikuIFRoZXNlIGVzdGltYXRlcyBhcmUgdXNlZCB0byBjYWxjdWxhdGUgdGhlIHBvc3RlcmlvciBwcm9iYWJpbGl0aWVzIHdoaWNoIGlzIHRoZSBwcm9iYWJpbGl0eSBvZiBhbiBvYnNlcnZhdGlvbiBiZWxvbmdpbmcgdG8gbWFsaWduYW50IG9yIGJlbmlnbiBnaXZlbiBpdHMgZXN0aW1hdGVkIHByZWRpY3RvciB2YWx1ZXMuIFRoaXMgY2FsY3VsYXRpb24gaXMgYmFzZWQgb24gQmF5ZXMnIHRoZW9yZW0sIHdoaWNoIGNvbWJpbmVzIHByaW9yIGtub3dsZWRnZSAocHJpb3IgcHJvYmFiaWxpdGllcykgYW5kIG5ldyBpbmZvcm1hdGlvbiAoZXN0aW1hdGVkIHByZWRpY3RvciB2YWx1ZXMpIHRvIGVzdGltYXRlIHRoZSBwcm9iYWJpbGl0eSBvZiBhbiBldmVudC4gCgpgYGB7ciAyYSwgcmVzdWx0cz0naGlkZSd9CiMjLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIExEQQojIyBhLgojIyBsaW5lYXIgZGlzY3JpbWluYW50IG1vZGVsCmxkYS53ZGJjID0gbGRhKERpZyB+IFYzICsgVjQsIGRhdGEgPSB0cmFpbl93ZGJjLCBjZW50ZXIgPSBUUlVFKSAKCmxkYV90YWJsZSA8LSBjYmluZChsZGEud2RiYyRwcmlvciwgbGRhLndkYmMkbWVhbikKY29sbmFtZXMobGRhX3RhYmxlKSA8LSBjKCJQcmlvciBwcm9iIG9mIGdyb3VwcyIsICJHcm91cCBtZWFucyhyYWRpdXMpIiwgIkdyb3VwIG1lYW5zKHRleHR1cmUpIikKCmBgYAoKYGBge3IgMmF0YWJsZX0Ka2FibGUobGRhX3RhYmxlKQpgYGAKCioqYi5Vc2UgdGhlIGZpdHRlZCBtb2RlbCBhbmQgQmF5ZXMgcnVsZSB0byBjb21wdXRlIHRoZSBwcmVkaWN0ZWQgb3V0Y29tZSBZy4YgZnJvbSB0aGUgcHJlZGljdGVkIHBvc3RlcmlvciBwcm9iYWJpbGl0aWVzLCBib3RoIG9uIHRoZSB0cmFpbmluZyBhbmQgdGVzdCBzZXQuIFRoZW4sIGNvbXB1dGUgdGhlIGNvbmZ1c2lvbiB0YWJsZSBhbmQgcHJlZGljdGlvbiBhY2N1cmFjeSBib3RoIG9uIHRoZSB0cmFpbmluZyBhbmQgdGVzdCBzZXQuIENvbW1lbnQgb24gdGhlIHJlc3VsdHMuKioKCiogUHJlZGljdGlvbiBhY2N1cmFjeSBvbiB0cmFpbmluZyBzZXQgPSAwLjg5CgoqIFByZWRpY3Rpb24gYWNjdXJhY3kgb24gdGVzdGluZyBzZXQgPSAwLjg3NgoKVGhlIHR3byBwcmVkaWN0aW9uIGFjY3VyYWN5IGFyZSBzaW1pbGFyLCBpdCBpbmRpY2F0ZXMgdGhhdCB0aGUgbW9kZWwgaGFzIGEgZ29vZCBwZXJmb3JtYW5jZS4gVGhlIHZhbHVlcyBhcmUgc2xpZ2h0bHkgc21hbGxlciB0aGFuIHRoZSBhY2N1cmFjeSBmcm9tIGZpdHRpbmcgdGhlIGxvZ2lzdGljIG1vZGVsLiBMREEgZmluZHMgdGhlIG9wdGltYWwgbGluZWFyIGNvbWJpbmF0aW9ucyBvZiBwcmVkaWN0b3JzIHRoYXQgbWF4aW1pemUgdGhlIHNlcGFyYXRpb24gYmV0d2VlbiBjbGFzc2VzIGFuZCB0aGVuIHVzZXMgQmF5ZXMnIHRoZW9yZW0gdG8gbWFrZSBwcmVkaWN0aW9ucywgd2hpbGUgbG9naXN0aWMgcmVncmVzc2lvbiB1c2VzIG1heGltdW0gbGlrZWxpaG9vZCBlc3RpbWF0aW9uIHRvIGZpdCBhIGxvZ2lzdGljIGZ1bmN0aW9uIHRvIHRoZSBkYXRhLiBJbiB0aGlzIHNjZW5hcmlvLCBpdCBzZWVtcyB0aGF0IHRoZSBsb2dpc3RpYyBtb2RlbCBpcyBtb3JlIGFwcHJvcHJpYXRlIGJlY2F1c2UgTERBIHdpdGggbGluZWFyIGNvbWJpbmF0aW9uIG1pZ2h0IGJlIHJlbGF0aXZlIHNpbXBsZXIgZm9yIHRoZSBkYXRhLiAKCmBgYHtyIDJiLCByZXN1bHRzPSdoaWRlJ30KIyMtLS0tLS0tLS0tLS0tLS0tLS0KIyMgYgpYX3RyYWluIDwtIHRyYWluX3dkYmMgJT4lIHNlbGVjdChjKFYzLCBWNCkpClhfdGVzdCA8LSB0ZXN0X3dkYmMgJT4lIHNlbGVjdChjKFYzLCBWNCkpCgojIyBmaXQgdGhlIG1vZGVsIGFuZCBjYWxjdWxhdGUgcG9zdCBwcm9iCnRyYWluX3ByZWQubGRhIDwtIHByZWRpY3QobGRhLndkYmMsIFhfdHJhaW4pJHBvc3Rlcmlvcgp0ZXN0X3ByZWQubGRhIDwtIHByZWRpY3QobGRhLndkYmMsIFhfdGVzdCkkcG9zdGVyaW9yCgojIyBjb21wdXRlIG91dGNvbWUgWSBiYXNlZCBvbiB0aGUgcG9zdCBwcm9iCnRyYWluX291dGNvbWUubGRhIDwtIGFwcGx5KHRyYWluX3ByZWQubGRhLCAxLCB3aGljaC5tYXgpCiMjIG91dGNvbWUgdmFyaWFibGUgPSAxIG9yIDIgCiMjIHRyYW5zZmVyIHRoZW0gdG8gQiBvciBNCnRyYWluX291dGNvbWUubGRhIDwtIGRhdGEuZnJhbWUodHJhaW5fb3V0Y29tZS5sZGEpCnRyYWluX291dGNvbWUubGRhJHRyYWluX291dGNvbWUubGRhIDwtIAogICAgICAgIGlmZWxzZSh0cmFpbl9vdXRjb21lLmxkYSR0cmFpbl9vdXRjb21lLmxkYT09MSwgIkIiLCAiTSIpCgojIyBEbyBpdCBhZ2FpbiwgYnV0IHVzaW5nIHRlc3Rpbmcgc2V0CnRlc3Rfb3V0Y29tZS5sZGEgPC0gYXBwbHkodGVzdF9wcmVkLmxkYSwgMSwgd2hpY2gubWF4KQojIyBvdXRjb21lIHZhcmlhYmxlID0gMSBvciAyIAojIyB0cmFuc2ZlciB0aGVtIHRvIEIgb3IgTQp0ZXN0X291dGNvbWUubGRhIDwtIGRhdGEuZnJhbWUodGVzdF9vdXRjb21lLmxkYSkKdGVzdF9vdXRjb21lLmxkYSR0ZXN0X291dGNvbWUubGRhIDwtIAogICAgICAgIGlmZWxzZSh0ZXN0X291dGNvbWUubGRhJHRlc3Rfb3V0Y29tZS5sZGE9PTEsICJCIiwgIk0iKQoKIyMgY29uZnVzaW9uIG1hdHJpeAp0cmFpbl9jb25mdXNpb24ubGRhIDwtIHRhYmxlKFByZWRpY3Rpb24gPSB0cmFpbl9vdXRjb21lLmxkYSR0cmFpbl9vdXRjb21lLmxkYSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQWN0dWFsID0gdHJhaW5fd2RiYyRWMikKdGVzdF9jb25mdXNpb24ubGRhIDwtIHRhYmxlKFByZWRpY3Rpb24gPSB0ZXN0X291dGNvbWUubGRhJHRlc3Rfb3V0Y29tZS5sZGEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgQWN0dWFsID0gdGVzdF93ZGJjJFYyKQoKIyMgcHJlZGljdGlvbiBhY2N1cmFjeQp0cmFpbl9hY2N1cmFjeS5sZGEgPC0gc3VtKGRpYWcodHJhaW5fY29uZnVzaW9uLmxkYSkpIC8gbnJvdyh0cmFpbl93ZGJjKQp0ZXN0X2FjY3VyYWN5LmxkYSA8LSBzdW0oZGlhZyh0ZXN0X2NvbmZ1c2lvbi5sZGEpKSAvIG5yb3codGVzdF93ZGJjKQoKYGBgCgpgYGB7ciAyYiB0YWJsZTF9CmthYmxlKHRyYWluX2NvbmZ1c2lvbi5sZGEsCiAgICAgIGNhcHRpb24gPSAiQ29uZnVzaW9uIHRhYmxlIGZvciB0cmFpbmluZyBzZXQgLSBMREEiKQoKYGBgCgpgYGB7ciAyYiB0YWJsZTJ9CmthYmxlKHRlc3RfY29uZnVzaW9uLmxkYSwKICAgICAgY2FwdGlvbiA9ICJDb25mdXNpb24gdGFibGUgZm9yIHRlc3Rpbmcgc2V0IC0gTERBIikKCmBgYAoKKipjLlBsb3QgYW4gaW1hZ2Ugb2YgdGhlIExEQSBkZWNpc2lvbiBib3VuZGFyeSAoZm9sbG93aW5nIHRoZSBzdGVwcyBpbiAxKGcpKS4gR2VuZXJhdGUgdGhlIHNhbWUgcGxvdCBmb3IgY3V0b2ZmIHZhbHVlcyBvZiAwLjI1IGFuZCAwLjc1LiBDb21tZW50IG9uIHRoZSByZXN1bHRzLioqCgpUaGUgaW5jcmVhc2UgaW4gdGhlIHJlZCByZWdpb24gKHByZWRpY3RlZCBhcyBiZW5pZ24gdHVtb3IpIGFzIHRoZSBjdXRvZmYgdmFsdWUgZGVjcmVhc2VzIFdoZW4gdGhlIGN1dG9mZiB2YWx1ZSBpcyBoaWdoLCB0aGUgbW9kZWwgd291bGQgYmUgbW9yZSBjb25zZXJ2YXRpdmUgaW4gY2xhc3NpZnlpbmcgYW4gb2JzZXJ2YXRpb24gYXMgYmVuaWduLCB3aGlsZSBhcyB0aGUgY3V0b2ZmIHZhbHVlIGRlY3JlYXNlLCB0aGUgbW9kZWwgYmVjb21lcyBtb3JlIGNvbmZpZGVudCBpbiBjbGFzc2lmeWluZyBhbiBvYnNlcnZhdGlvbiBhcyBiZW5pZ24uIAoKYGBge3IgMmMsIHJlc3VsdHM9J2hpZGUnfQojIy0tLS0tLS0tLS0tLS0tLS0tLQojIyBjLgojIyB1c2UgdGhlIGZpdHRlZCBtb2RlbCB0byBwcmVkaWN0IHRoZSBvdXRjb21lIHByb2JhYmlsaXRpZXMgZm9yIGVhY2ggb2JzZXJ2YXRpb24gaW4gdGhlIGRlbnNlIHNldApwcm9icy5sZGEgPC0gcHJlZGljdChsZGEud2RiYywgbmV3ZGF0YSA9IGRlbnNlX3NldCwgdHlwZSA9ICJyZXNwb25zZSIpJHBvc3RlcmlvcgoKIyBDb21wdXRlIHRoZSBwcmVkaWN0ZWQgb3V0Y29tZXMgdXNpbmcgQmF5ZXMgcnVsZSB3aXRoIGRpZmZlcmVudCBwcm9iYWJpbGl0eSBjdXRvZmYgdmFsdWVzCnByZWRfb3V0Y29tZXNfMDUgPSBpZmVsc2UocHJvYnMubGRhWywxXSA+IHByb2JzLmxkYVssMl0sICJCIiwgIk0iKQpwcmVkX291dGNvbWVzXzI1ID0gaWZlbHNlKHByb2JzLmxkYVssMV0gPiAwLjI1LCAiQiIsICJNIikKcHJlZF9vdXRjb21lc183NSA9IGlmZWxzZShwcm9icy5sZGFbLDFdID4gMC43NSwgIkIiLCAiTSIpCgpgYGAKCmBgYHtyIDJjX3Bsb3QxLCBlY2hvPUZBTFNFLCBvdXQud2lkdGggPSAiNzAlIiwgZmlnLmFsaWduID0gJ2NlbnRlcid9CiMjIFBsb3QgdGhlIHByZWRpY3RlZCBvdXRjb21lcyBmb3IgcHJvYmFiaWxpdHkgY3V0b2ZmIG9mIDAuNQpnZ3Bsb3QoZGF0YSA9IGRlbnNlX3NldCwgYWVzKHggPSBWMywgeSA9IFY0LCBjb2xvciA9IGZhY3RvcihwcmVkX291dGNvbWVzXzA1KSkpICsKICAgICAgICBnZW9tX3BvaW50KGFscGhhID0gMC4yKSArCiAgICAgICAgZ2VvbV9wb2ludChkYXRhID0gdHJhaW5fd2RiYywgYWVzKHggPSBWMywgeSA9IFY0LCBjb2xvciA9IGZhY3RvcihWMikpKSArCiAgICAgICAgc2NhbGVfY29sb3JfZGlzY3JldGUobmFtZSA9ICJQcmVkaWN0aW9uIikgKwogICAgICAgIGdndGl0bGUoIkxEQSBEZWNpc2lvbiBCb3VuZGFyeSAoQ3V0b2ZmID0gMC41KSIpICsKICAgICAgICB4bGFiKCJBdmVyYWdlIHJhZGl1cyIpICsgeWxhYigiQXZlcmFnZSB0ZXh0dXJlIikKYGBgCgpgYGB7ciAyY19wbG90MiwgZWNobz1GQUxTRSwgb3V0LndpZHRoID0gIjcwJSIsIGZpZy5hbGlnbiA9ICdjZW50ZXInfQojIFBsb3QgdGhlIHByZWRpY3RlZCBvdXRjb21lcyBmb3IgYSBwcm9iYWJpbGl0eSBjdXRvZmYgb2YgMC4yNQpnZ3Bsb3QoZGF0YSA9IGRlbnNlX3NldCwgYWVzKHggPSBWMywgeSA9IFY0LCBjb2xvciA9IGZhY3RvcihwcmVkX291dGNvbWVzXzI1KSkpICsKICAgICAgICBnZW9tX3BvaW50KGFscGhhID0gMC4yKSArCiAgICAgICAgZ2VvbV9wb2ludChkYXRhID0gdHJhaW5fd2RiYywgYWVzKHggPSBWMywgeSA9IFY0LCBjb2xvciA9IGZhY3RvcihWMikpKSArCiAgICAgICAgc2NhbGVfY29sb3JfZGlzY3JldGUobmFtZSA9ICJQcmVkaWN0aW9uIikgKwogICAgICAgIGdndGl0bGUoIkxEQSBEZWNpc2lvbiBCb3VuZGFyeSAoQ3V0b2ZmID0gMC4yNSkiKSArCiAgICAgICAgeGxhYigiQXZlcmFnZSByYWRpdXMiKSArIHlsYWIoIkF2ZXJhZ2UgdGV4dHVyZSIpCmBgYAoKYGBge3IgMmNfcGxvdDMsIGVjaG89RkFMU0UsIG91dC53aWR0aCA9ICI3MCUiLCBmaWcuYWxpZ24gPSAnY2VudGVyJ30KIyBQbG90IHRoZSBwcmVkaWN0ZWQgb3V0Y29tZXMgZm9yIGEgcHJvYmFiaWxpdHkgY3V0b2ZmIG9mIDAuNzUKZ2dwbG90KGRhdGEgPSBkZW5zZV9zZXQsIGFlcyh4ID0gVjMsIHkgPSBWNCwgY29sb3IgPSBmYWN0b3IocHJlZF9vdXRjb21lc183NSkpKSArCiAgICAgICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMikgKwogICAgICAgIGdlb21fcG9pbnQoZGF0YSA9IHRyYWluX3dkYmMsIGFlcyh4ID0gVjMsIHkgPSBWNCwgY29sb3IgPSBmYWN0b3IoVjIpKSkgKwogICAgICAgIHNjYWxlX2NvbG9yX2Rpc2NyZXRlKG5hbWUgPSAiUHJlZGljdGlvbiIpICsKICAgICAgICBnZ3RpdGxlKCJMREEgRGVjaXNpb24gQm91bmRhcnkgKEN1dG9mZiA9IDAuNzUpIikgKwogICAgICAgIHhsYWIoIkF2ZXJhZ2UgcmFkaXVzIikgKyB5bGFiKCJBdmVyYWdlIHRleHR1cmUiKQpgYGAKCioqZC5QbG90IHRoZSBST0MgY3VydmUsIGNvbXB1dGVkIG9uIHRoZSB0ZXN0IHNldC4qKgoKYGBge3IgMmQsIGVjaG89RkFMU0UsIG91dC53aWR0aCA9ICI2MCUiLCBmaWcuYWxpZ24gPSAnY2VudGVyJ30KIyMtLS0tLS0tLS0tLS0tLS0tLS0KIyMgZC4KdGVzdC5sZGFfcHJvYnMgPC0gcHJlZGljdChsZGEud2RiYywgbmV3ZGF0YSA9IHRlc3Rfd2RiYywgIHR5cGUgPSAicmVzcG9uc2UiKSRwb3N0ZXJpb3JbLCAyXQojIyBjYWxjdWxhdGUgQVVDIHNjb3JlCnJvYy5sZGFfc2NvcmUgPSByb2MocmVzcG9uc2UgPSB0ZXN0X3dkYmMkVjIsIHByZWRpY3RvciA9IHRlc3QubGRhX3Byb2JzKSAgCiNyb2MubGRhX3Njb3JlCiMgMC45Mzk2CgojIyBwbG90IHRoZSBST0MgY3VydmUKZ2dyb2Mocm9jLmxkYV9zY29yZSwgbGluZXR5cGU9MSwgc2l6ZSA9IDEpICsgCiAgICAgICAgeGxhYigiRlBSIikgKyB5bGFiKCJUUFIiKSArCiAgICAgICAgZ2d0aXRsZSgiVGVzdCBST0MgY3VydmUgZm9yIExEQSIpCgpgYGAKCioqZS5Db21wdXRlIGFuIGVzdGltYXRlIG9mIHRoZSBBcmVhIHVuZGVyIHRoZSBST0MgQ3VydmUgKEFVQykuKioKClRoZSBBVUMgc2NvcmUgaXMgMC45Mzk2CgpgYGB7ciAyZSwgaW5jbHVkZT1GQUxTRX0Kcm9jLmxkYV9zY29yZQojIDAuOTM5NgpgYGAKCiMjIDMuUXVhZHJhdGljIERpc2NyaW1pbmFudCBBbmFseXNpcyBNb2RlbC4KCioqYS5Ob3cgZml0IGEgbGluZWFyIGRpc2NyaW1pbmFudCBhbmFseXNpcyBtb2RlbCB0byB0aGUgdHJhaW5pbmcgc2V0IHlvdSBjcmVhdGVkIGluIEV4ZXJjaXNlIDEuIE1ha2UgYSB0YWJsZSBkaXNwbGF5aW5nIHRoZSBlc3RpbWF0ZWQg4oCYUHJpb3IgcHJvYmFiaWxpdGllcyBvZiBncm91cHPigJkgYW5kIOKAmEdyb3VwIG1lYW5z4oCZLiBEZXNjcmliZSBpbiB3b3JkcyB0aGUgbWVhbmluZyBvZiB0aGVzZSBlc3RpbWF0ZXMgYW5kIGhvdyB0aGV5IGFyZSByZWxhdGVkIHRvIHRoZSBwb3N0ZXJpb3IgcHJvYmFiaWxpdGllcy4qKgoKVGhlIGVzdGltYXRlZCBncm91cCBtZWFucyBhcmUgdGhlIGF2ZXJhZ2Ugb2YgdGhlIHByZWRpY3RvciB2YXJpYWJsZXMgKHJhZGl1cyBvciB0ZXh0dXJlKSBmb3IgZWFjaCBncm91cCAobWFsaWduYW50IG9yIGJlbmlnbikuIFRoZXNlIGVzdGltYXRlcyBhcmUgdXNlZCB0byBjYWxjdWxhdGUgdGhlIHBvc3RlcmlvciBwcm9iYWJpbGl0aWVzIHdoaWNoIGlzIHRoZSBwcm9iYWJpbGl0eSBvZiBhbiBvYnNlcnZhdGlvbiBiZWxvbmdpbmcgdG8gbWFsaWduYW50IG9yIGJlbmlnbiBnaXZlbiBpdHMgZXN0aW1hdGVkIHByZWRpY3RvciB2YWx1ZXMuIFRoaXMgY2FsY3VsYXRpb24gaXMgYmFzZWQgb24gQmF5ZXMnIHRoZW9yZW0sIHdoaWNoIGNvbWJpbmVzIHByaW9yIGtub3dsZWRnZSAocHJpb3IgcHJvYmFiaWxpdGllcykgYW5kIG5ldyBpbmZvcm1hdGlvbiAoZXN0aW1hdGVkIHByZWRpY3RvciB2YWx1ZXMpIHRvIGVzdGltYXRlIHRoZSBwcm9iYWJpbGl0eSBvZiBhbiBldmVudC4gCgpgYGB7ciAzYSwgcmVzdWx0cz0naGlkZSd9CiMjIGEuCiMjIHF1YWRyYXRpYyBkaXNjcmltaW5hbnQgYW5hbHlzaXMgCnFkYS53ZGJjID0gcWRhKERpZyB+IFYzICsgVjQsIGRhdGEgPSB0cmFpbl93ZGJjLCBjZW50ZXIgPSBUUlVFKQpxZGEud2RiYwpxZGFfdGFibGUgPC0gY2JpbmQocWRhLndkYmMkcHJpb3IsIHFkYS53ZGJjJG1lYW4pCmNvbG5hbWVzKHFkYV90YWJsZSkgPC0gYygiUHJpb3IgcHJvYiBvZiBncm91cHMiLCAiR3JvdXAgbWVhbnMocmFkaXVzKSIsICJHcm91cCBtZWFucyh0ZXh0dXJlKSIpCgpgYGAKCmBgYHtyIDNhXzIgdGFibGV9CmthYmxlKHFkYV90YWJsZSkKYGBgCgoqKmIuVXNlIHRoZSBmaXR0ZWQgbW9kZWwgYW5kIEJheWVzIHJ1bGUgdG8gY29tcHV0ZSB0aGUgcHJlZGljdGVkIG91dGNvbWUgWcuGIGZyb20gdGhlIHByZWRpY3RlZCBwb3N0ZXJpb3IgcHJvYmFiaWxpdGllcywgYm90aCBvbiB0aGUgdHJhaW5pbmcgYW5kIHRlc3Qgc2V0LiBUaGVuLCBjb21wdXRlIHRoZSBjb25mdXNpb24gdGFibGUgYW5kIHByZWRpY3Rpb24gYWNjdXJhY3kgYm90aCBvbiB0aGUgdHJhaW5pbmcgYW5kIHRlc3Qgc2V0LiBDb21tZW50IG9uIHRoZSByZXN1bHRzLioqCgoqIFByZWRpY3Rpb24gYWNjdXJhY3kgb24gdHJhaW5pbmcgc2V0ID0gMC44ODUKCiogUHJlZGljdGlvbiBhY2N1cmFjeSBvbiB0ZXN0aW5nIHNldCA9IDAuODcwCgpUaGUgdHdvIHByZWRpY3Rpb24gYWNjdXJhY3kgYXJlIHNpbWlsYXIsIHdoaWNoIGluZGljYXRlcyB0aGF0IHRoZSBtb2RlbCBmaXRzIHdlbGwuIFRoZSB2YWx1ZXMgYXJlIHNpbWlsYXIgdG8gdGhlIG9uZXMgZnJvbSBMREEuIEl0IHNlZW1zIHRoYXQgdXNpbmcgTERBIG9yIFFEQSBpcyBub3Qgc28gbXVjaCBkaWZmZXJlbnQuIAoKYGBge3IgM2IsIHJlc3VsdHM9J2hpZGUnfQojIy0tLS0tLS0tLS0tLS0tLS0tLQojIyBiLgojIyBmaXQgdGhlIG1vZGVsIGFuZCBjYWxjdWxhdGUgcG9zdCBwcm9iCnRyYWluX3ByZWQucWRhIDwtIHByZWRpY3QocWRhLndkYmMsIFhfdHJhaW4pJHBvc3Rlcmlvcgp0ZXN0X3ByZWQucWRhIDwtIHByZWRpY3QocWRhLndkYmMsIFhfdGVzdCkkcG9zdGVyaW9yCgojIyBjb21wdXRlIG91dGNvbWUgWSBiYXNlZCBvbiB0aGUgcG9zdCBwcm9iIG9uIHRyYWluaW5nIHNldAp0cmFpbl9vdXRjb21lLnFkYSA8LSBhcHBseSh0cmFpbl9wcmVkLnFkYSwgMSwgd2hpY2gubWF4KQojIyBvdXRjb21lIHZhcmlhYmxlID0gMSBvciAyIAojIyB0cmFuc2ZlciB0aGVtIHRvIEIgb3IgTQp0cmFpbl9vdXRjb21lLnFkYSA8LSBkYXRhLmZyYW1lKHRyYWluX291dGNvbWUucWRhKQp0cmFpbl9vdXRjb21lLnFkYSR0cmFpbl9vdXRjb21lLnFkYSA8LSAKICAgICAgICBpZmVsc2UodHJhaW5fb3V0Y29tZS5xZGEkdHJhaW5fb3V0Y29tZS5xZGE9PTEsICJCIiwgIk0iKQoKIyMgY29tcHV0ZSBvdXRjb21lIFkgYmFzZWQgb24gdGhlIHBvc3QgcHJvYiBvbiB0ZXN0aW5nIHNldAp0ZXN0X291dGNvbWUucWRhIDwtIGFwcGx5KHRlc3RfcHJlZC5xZGEsIDEsIHdoaWNoLm1heCkKIyMgb3V0Y29tZSB2YXJpYWJsZSA9IDEgb3IgMiAKIyMgdHJhbnNmZXIgdGhlbSB0byBCIG9yIE0KdGVzdF9vdXRjb21lLnFkYSA8LSBkYXRhLmZyYW1lKHRlc3Rfb3V0Y29tZS5xZGEpCnRlc3Rfb3V0Y29tZS5xZGEkdGVzdF9vdXRjb21lLnFkYSA8LSAKICAgICAgICBpZmVsc2UodGVzdF9vdXRjb21lLnFkYSR0ZXN0X291dGNvbWUucWRhPT0xLCAiQiIsICJNIikKCiMjIGNvbmZ1c2lvbiBtYXRyaXgKdHJhaW5fY29uZnVzaW9uLnFkYSA8LSB0YWJsZShQcmVkaWN0aW9uID0gdHJhaW5fb3V0Y29tZS5xZGEkdHJhaW5fb3V0Y29tZS5xZGEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFjdHVhbCA9IHRyYWluX3dkYmMkVjIpCnRlc3RfY29uZnVzaW9uLnFkYSA8LSB0YWJsZShQcmVkaWN0aW9uID0gdGVzdF9vdXRjb21lLnFkYSR0ZXN0X291dGNvbWUucWRhLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFjdHVhbCA9IHRlc3Rfd2RiYyRWMikKCiMjIHByZWRpY3Rpb24gYWNjdXJhY3kKdHJhaW5fYWNjdXJhY3kucWRhIDwtIHN1bShkaWFnKHRyYWluX2NvbmZ1c2lvbi5xZGEpKSAvIG5yb3codHJhaW5fd2RiYykKdGVzdF9hY2N1cmFjeS5xZGEgPC0gc3VtKGRpYWcodGVzdF9jb25mdXNpb24ucWRhKSkgLyBucm93KHRlc3Rfd2RiYykKCmBgYAoKYGBge3IgM2IgdGFibGUxfQprYWJsZSh0cmFpbl9jb25mdXNpb24ucWRhLAogICAgICBjYXB0aW9uID0gIkNvbmZ1c2lvbiB0YWJsZSBmb3IgdHJhaW5pbmcgc2V0IC0gUURBIikKCmBgYAoKYGBge3IgM2IgdGFibGUyfQprYWJsZSh0ZXN0X2NvbmZ1c2lvbi5xZGEsCiAgICAgIGNhcHRpb24gPSAiQ29uZnVzaW9uIHRhYmxlIGZvciB0ZXN0aW5nIHNldCAtIFFEQSIpCgpgYGAKCioqYy4oYykgUGxvdCBhbiBpbWFnZSBvZiB0aGUgUURBIGRlY2lzaW9uIGJvdW5kYXJ5IChmb2xsb3dpbmcgdGhlIHN0ZXBzIGluIDEoZykpLiBHZW5lcmF0ZSB0aGUgc2FtZSBwbG90IGZvciBjdXRvZmYgdmFsdWVzIG9mIDAuMjUgYW5kIDAuNzUuIENvbW1lbnQgb24gdGhlIHJlc3VsdHMuKioKClRoZSBpbmNyZWFzZSBpbiB0aGUgcmVkIHJlZ2lvbiAocHJlZGljdGVkIGFzIGJlbmlnbiB0dW1vcikgYXMgdGhlIGN1dG9mZiB2YWx1ZSBkZWNyZWFzZXMgV2hlbiB0aGUgY3V0b2ZmIHZhbHVlIGlzIGhpZ2gsIHRoZSBtb2RlbCB3b3VsZCBiZSBtb3JlIGNvbnNlcnZhdGl2ZSBpbiBjbGFzc2lmeWluZyBhbiBvYnNlcnZhdGlvbiBhcyBiZW5pZ24sIHdoaWxlIGFzIHRoZSBjdXRvZmYgdmFsdWUgZGVjcmVhc2UsIHRoZSBtb2RlbCBiZWNvbWVzIG1vcmUgY29uZmlkZW50IGluIGNsYXNzaWZ5aW5nIGFuIG9ic2VydmF0aW9uIGFzIGJlbmlnbi4gCgpgYGB7ciAzYywgcmVzdWx0cz0naGlkZSd9CiMjLS0tLS0tLS0tLS0tLS0tLS0tCiMjIGMuCiMjIHVzZSB0aGUgZml0dGVkIEdMTSBtb2RlbCB0byBwcmVkaWN0IHRoZSBvdXRjb21lIHByb2JhYmlsaXRpZXMgZm9yIGVhY2ggb2JzZXJ2YXRpb24gaW4gdGhlIGRlbnNlIHNldApwcm9icy5xZGEgPC0gcHJlZGljdChxZGEud2RiYywgbmV3ZGF0YSA9IGRlbnNlX3NldCwgdHlwZSA9ICJyZXNwb25zZSIpJHBvc3RlcmlvcgoKIyBDb21wdXRlIHRoZSBwcmVkaWN0ZWQgb3V0Y29tZXMgdXNpbmcgQmF5ZXMgcnVsZSB3aXRoIGRpZmZlcmVudCBwcm9iYWJpbGl0eSBjdXRvZmYgdmFsdWVzCnByZWRfb3V0Y29tZXNfMDUucWRhID0gaWZlbHNlKHByb2JzLnFkYVssMV0gPiBwcm9icy5xZGFbLDJdLCAiQiIsICJNIikKcHJlZF9vdXRjb21lc18yNS5xZGEgPSBpZmVsc2UocHJvYnMucWRhWywxXSA+IDAuMjUsICJCIiwgIk0iKQpwcmVkX291dGNvbWVzXzc1LnFkYSA9IGlmZWxzZShwcm9icy5xZGFbLDFdID4gMC43NSwgIkIiLCAiTSIpCgpgYGAKCmBgYHtyIDNjX3Bsb3QxLCBlY2hvPUZBTFNFLCBvdXQud2lkdGggPSAiNzAlIiwgZmlnLmFsaWduID0gJ2NlbnRlcid9CiMjIFBsb3QgdGhlIHByZWRpY3RlZCBvdXRjb21lcyBmb3IgcHJvYmFiaWxpdHkgY3V0b2ZmIG9mIDAuNQpnZ3Bsb3QoZGF0YSA9IGRlbnNlX3NldCwgYWVzKHggPSBWMywgeSA9IFY0LCBjb2xvciA9IGZhY3RvcihwcmVkX291dGNvbWVzXzA1LnFkYSkpKSArCiAgICAgICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMikgKwogICAgICAgIGdlb21fcG9pbnQoZGF0YSA9IHRyYWluX3dkYmMsIGFlcyh4ID0gVjMsIHkgPSBWNCwgY29sb3IgPSBmYWN0b3IoVjIpKSkgKwogICAgICAgIHNjYWxlX2NvbG9yX2Rpc2NyZXRlKG5hbWUgPSAiUHJlZGljdGlvbiIpICsKICAgICAgICBnZ3RpdGxlKCJRREEgRGVjaXNpb24gQm91bmRhcnkgKEN1dG9mZiA9IDAuNSkiKSArCiAgICAgICAgeGxhYigiQXZlcmFnZSByYWRpdXMiKSArIHlsYWIoIkF2ZXJhZ2UgdGV4dHVyZSIpCmBgYAoKYGBge3IgM2NfcGxvdDIsIGVjaG89RkFMU0UsIG91dC53aWR0aCA9ICI3MCUiLCBmaWcuYWxpZ24gPSAnY2VudGVyJ30KIyMgUGxvdCB0aGUgcHJlZGljdGVkIG91dGNvbWVzIGZvciBwcm9iYWJpbGl0eSBjdXRvZmYgb2YgMC4yNQpnZ3Bsb3QoZGF0YSA9IGRlbnNlX3NldCwgYWVzKHggPSBWMywgeSA9IFY0LCBjb2xvciA9IGZhY3RvcihwcmVkX291dGNvbWVzXzI1LnFkYSkpKSArCiAgICAgICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMikgKwogICAgICAgIGdlb21fcG9pbnQoZGF0YSA9IHRyYWluX3dkYmMsIGFlcyh4ID0gVjMsIHkgPSBWNCwgY29sb3IgPSBmYWN0b3IoVjIpKSkgKwogICAgICAgIHNjYWxlX2NvbG9yX2Rpc2NyZXRlKG5hbWUgPSAiUHJlZGljdGlvbiIpICsKICAgICAgICBnZ3RpdGxlKCJRREEgRGVjaXNpb24gQm91bmRhcnkgKEN1dG9mZiA9IDAuMjUpIikgKwogICAgICAgIHhsYWIoIkF2ZXJhZ2UgcmFkaXVzIikgKyB5bGFiKCJBdmVyYWdlIHRleHR1cmUiKQpgYGAKCmBgYHtyIDNjX3Bsb3QzLCBlY2hvPUZBTFNFLCBvdXQud2lkdGggPSAiNzAlIiwgZmlnLmFsaWduID0gJ2NlbnRlcid9CiMjIFBsb3QgdGhlIHByZWRpY3RlZCBvdXRjb21lcyBmb3IgcHJvYmFiaWxpdHkgY3V0b2ZmIG9mIDAuNzUKZ2dwbG90KGRhdGEgPSBkZW5zZV9zZXQsIGFlcyh4ID0gVjMsIHkgPSBWNCwgY29sb3IgPSBmYWN0b3IocHJlZF9vdXRjb21lc183NS5xZGEpKSkgKwogICAgICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjIpICsKICAgICAgICBnZW9tX3BvaW50KGRhdGEgPSB0cmFpbl93ZGJjLCBhZXMoeCA9IFYzLCB5ID0gVjQsIGNvbG9yID0gZmFjdG9yKFYyKSkpICsKICAgICAgICBzY2FsZV9jb2xvcl9kaXNjcmV0ZShuYW1lID0gIlByZWRpY3Rpb24iKSArCiAgICAgICAgZ2d0aXRsZSgiUURBIERlY2lzaW9uIEJvdW5kYXJ5IChDdXRvZmYgPSAwLjc1KSIpICsKICAgICAgICB4bGFiKCJBdmVyYWdlIHJhZGl1cyIpICsgeWxhYigiQXZlcmFnZSB0ZXh0dXJlIikKYGBgCgoqKmQuUGxvdCB0aGUgUk9DIGN1cnZlLCBjb21wdXRlZCBvbiB0aGUgdGVzdCBzZXQuKioKCmBgYHtyIDNkLCBlY2hvPUZBTFNFLCBvdXQud2lkdGggPSAiNjAlIiwgZmlnLmFsaWduID0gJ2NlbnRlcid9CiMjLS0tLS0tLS0tLS0tLS0tLS0tCiMjIGQuCnRlc3QucWRhX3Byb2JzIDwtIHByZWRpY3QocWRhLndkYmMsIG5ld2RhdGEgPSB0ZXN0X3dkYmMsIHR5cGUgPSAicmVzcG9uc2UiKSRwb3N0ZXJpb3IKIyMgY2FsY3VsYXRlIEFVQyBzY29yZQpyb2MucWRhX3Njb3JlID0gcm9jKHJlc3BvbnNlID0gdGVzdF93ZGJjJFYyLCBwcmVkaWN0b3IgPSB0ZXN0LnFkYV9wcm9ic1ssIDJdKSAgCiNyb2MucWRhX3Njb3JlCiMgMC45MzYxCgojIyBwbG90IHRoZSBST0MgY3VydmUKZ2dyb2Mocm9jLnFkYV9zY29yZSwgbGluZXR5cGU9MSwgc2l6ZSA9IDEpICsgCiAgICAgICAgeGxhYigiRlBSIikgKyB5bGFiKCJUUFIiKSArCiAgICAgICAgZ2d0aXRsZSgiVGVzdCBST0MgY3VydmUgZm9yIFFEQSIpCgpgYGAKCioqZS5Db21wdXRlIGFuIGVzdGltYXRlIG9mIHRoZSBBcmVhIHVuZGVyIHRoZSBST0MgQ3VydmUgKEFVQykuKioKClRoZSBBVUMgc2NvcmUgaXMgMC45MzYxCgpgYGB7ciAzZSwgaW5jbHVkZT1GQUxTRX0Kcm9jLnFkYV9zY29yZQojIDAuOTM2MQpgYGAKCiMjIDQuS05OIENsYXNzaWZpZXIuCgoqKmEuRm9yIGFsbCBjaG9pY2VzIG9mIGsgPSB7MSwgMiwgMywgNCwgMjB9IChudW1iZXIgb2YgbmVpZ2hib3JzKSwgY29tcHV0ZSB0aGUgcHJlZGljdGVkIG91dGNvbWUgWcuGIGZvciBlYWNoIG9ic2VydmF0aW9uIGluIHRoZSB0cmFpbmluZyBhbmQgdGVzdCBzZXQuIFRoZW4sIGNvbXB1dGUgdGhlIGNvbmZ1c2lvbiB0YWJsZSBhbmQgcHJlZGljdGlvbiBhY2N1cmFjeSBib3RoIG9uIHRoZSB0cmFpbmluZyBhbmQgdGVzdCBzZXQuIENvbW1lbnQgb24gdGhlIHJlc3VsdHMuKioKCmBgYHtyIDRhLCByZXN1bHRzPSdoaWRlJ30KIyMtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gS05OCiMjIGEuCiMjIGtubiAKa192YWx1ZXMgPC0gYygxLCAyLCAzLCA0LCAyMCkKZm9yIChrIGluIGtfdmFsdWVzKSB7CiAgICAgICAgIyB0cmFpbiBzZXQKICAgICAgICBrbm4udHJhaW4ubGFiZWwgPC0ga25uKHRyYWluID0gdHJhaW5fd2RiY1ssYygiVjMiLCAiVjQiKV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IHRyYWluX3dkYmNbLGMoIlYzIiwgIlY0IildLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsID0gdHJhaW5fd2RiYyRWMiwgayA9IGspCiAgICAgICAgCiAgICAgICAgIyBjb25mdXNpb24gdGFibGUgCiAgICAgICAgY29uZnVzaW9uX3RhYmxlX3RyYWluIDwtIHRhYmxlKGtubi50cmFpbi5sYWJlbCwgdHJhaW5fd2RiYyRWMikKICAgICAgICAjIHByZWRpY3Rpb24gYWNjdXJhY3kKICAgICAgICBhY2N1cmFjeV90cmFpbiA8LSBzdW0oZGlhZyhjb25mdXNpb25fdGFibGVfdHJhaW4pKSAvIHN1bShjb25mdXNpb25fdGFibGVfdHJhaW4pCiAgICAgICAgCiAgICAgICAgIyB0ZXN0IHNldAogICAgICAgIGtubi50ZXN0LmxhYmVsIDwtIGtubih0cmFpbiA9IHRyYWluX3dkYmNbLGMoIlYzIiwgIlY0IildLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IHRlc3Rfd2RiY1ssYygiVjMiLCAiVjQiKV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbCA9IHRyYWluX3dkYmMkVjIsIGsgPSBrKQogICAgICAgIAogICAgICAgICMgY29uZnVzaW9uIHRhYmxlIC0gdGVzdCBzZXQKICAgICAgICBjb25mdXNpb25fdGFibGVfdGVzdCA8LSB0YWJsZShrbm4udGVzdC5sYWJlbCwgdGVzdF93ZGJjJFYyKQogICAgICAgICMgcHJlZGljdGlvbiBhY2N1cmFjeQogICAgICAgIGFjY3VyYWN5X3Rlc3QgPC0gc3VtKGRpYWcoY29uZnVzaW9uX3RhYmxlX3Rlc3QpKSAvIHN1bShjb25mdXNpb25fdGFibGVfdGVzdCkKICAgICAgICAKICAgICAgICAjIFByaW50IHRoZSByZXN1bHRzCiAgICAgICAgY2F0KCJGb3IgayA9IiwgaywgIjpcbiIpCiAgICAgICAgY2F0KCJUcmFpbmluZyBzZXQgY29uZnVzaW9uIHRhYmxlOlxuIikKICAgICAgICBwcmludChjb25mdXNpb25fdGFibGVfdHJhaW4pCiAgICAgICAgY2F0KCJUcmFpbmluZyBzZXQgYWNjdXJhY3k6IiwgYWNjdXJhY3lfdHJhaW4sICJcbiIpCiAgICAgICAgY2F0KCJUZXN0IHNldCBjb25mdXNpb24gdGFibGU6XG4iKQogICAgICAgIHByaW50KGNvbmZ1c2lvbl90YWJsZV90ZXN0KQogICAgICAgIGNhdCgiVGVzdCBzZXQgYWNjdXJhY3k6IiwgYWNjdXJhY3lfdGVzdCwgIlxuXG4iKQp9CgpgYGAKCioqYi5QbG90IGFuIGltYWdlIG9mIHRoZSBkZWNpc2lvbiBib3VuZGFyeSAoZm9sbG93aW5nIHRoZSBzdGVwcyBpbiAxKGcpKSwgZm9yIGsgPSB7MSwgMiwgMywgNCwgMjB9IChudW1iZXIgb2YgbmVpZ2hib3JzKS4gQ29tbWVudCBvbiB0aGUgcmVzdWx0cy4qKgoKQmFzZWQgb24gdGhlIEtOTiBkZWNpc2lvbiBib3VuZGFyeSBwbG90cyB3aXRoIGRpZmZlcmVudCB2YWx1ZXMgb2Ygaywgd2UgY2FuIHNlZSB0aGF0IHRoZSBib3VuZGFyaWVzIGFyZSBsZXNzIHNwZWNpZmljIHRoYW4gdGhlIGJvdW5kYXJpZXMgdXNpbmcgbG9naXN0aWMgcmVncmVzc2lvbiwgTERBLCBhbmQgUURBLiBJdCBtYWtlcyBzZW5zZSBiZWNhdXNlIEtOTiBtb2RlbHMgbWFrZSBwcmVkaWN0aW9ucyBiYXNlZCBvbiB0aGUgY2xhc3NlcyBvZiB0aGUgbmVhcmVzdCBuZWlnaGJvcnMsIHdoZXJlYXMgbG9naXN0aWMgcmVncmVzc2lvbiwgTERBLCBhbmQgUURBIGFyZSBiYXNlZCBvbiBhIG1vcmUgc29waGlzdGljYXRlZCBtYXRoZW1hdGljYWwgbW9kZWwuIEluIGFkZGl0aW9uLCBhY2NvcmRpbmcgdG8gdGhlIGZpdmUgcGxvdHMgd2l0aCBkaWZmZXJlbnQgdmFsdWVzIG9mIGssIHdlIGtub3cgdGhhdCB0aGUgZGVjaXNpb24gYm91bmRhcnkgaXMgbW9yZSBwcmVjaXNlIHdoZW4gaz0zLiBJdCBzZWVtcyB0aGF0IGs9MyBtaWdodCBiZSBtb3JlIGFwcHJvcHJpYXRlIGluIHRoaXMgc2NlbmFyaW8uIAoKYGBge3IgNGJfazEsIGVjaG89RkFMU0UsIG91dC53aWR0aCA9ICI3MCUiLCBmaWcuYWxpZ24gPSAnY2VudGVyJ30KIyMtLS0tLS0tLS0tLS0tLS0tLS0KIyMgYi4KIyMgaz0xCm1vZGVsLmtubi4xID0ga25uKHRyYWluID0gdHJhaW5fd2RiY1ssYygiVjMiLCAiVjQiKV0sIAogICAgICAgICAgICB0ZXN0ID0gZGVuc2Vfc2V0WyxjKCJWMyIsICJWNCIpXSwgCiAgICAgICAgICAgIGNsID0gdHJhaW5fd2RiYyRWMiwgCiAgICAgICAgICAgIGsgPSAxKQoKIyMgUGxvdCAKZ2dwbG90KGRhdGEgPSBkZW5zZV9zZXQsIGFlcyh4ID0gVjMsIHkgPSBWNCwgY29sb3IgPSBmYWN0b3IobW9kZWwua25uLjEpKSkgKwogICAgICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjIpICsKICAgICAgICBnZW9tX3BvaW50KGRhdGEgPSB0cmFpbl93ZGJjLCBhZXMoeCA9IFYzLCB5ID0gVjQsIGNvbG9yID0gZmFjdG9yKFYyKSkpICsKICAgICAgICBzY2FsZV9jb2xvcl9kaXNjcmV0ZShuYW1lID0gIlByZWRpY3Rpb24iKSArCiAgICAgICAgZ2d0aXRsZSgiS05OIERlY2lzaW9uIEJvdW5kYXJ5IChrPTEpIikgKwogICAgICAgIHhsYWIoIkF2ZXJhZ2UgcmFkaXVzIikgKyB5bGFiKCJBdmVyYWdlIHRleHR1cmUiKQoKYGBgCgpgYGB7ciA0Yl9rMiwgZWNobz1GQUxTRSwgb3V0LndpZHRoID0gIjcwJSIsIGZpZy5hbGlnbiA9ICdjZW50ZXInfQojIyBrPTIKbW9kZWwua25uLjIgPSBrbm4odHJhaW4gPSB0cmFpbl93ZGJjWyxjKCJWMyIsICJWNCIpXSwgCiAgICAgICAgICAgICAgICAgIHRlc3QgPSBkZW5zZV9zZXRbLGMoIlYzIiwgIlY0IildLCAKICAgICAgICAgICAgICAgICAgY2wgPSB0cmFpbl93ZGJjJFYyLCAKICAgICAgICAgICAgICAgICAgayA9IDIpCgojIyBQbG90IApnZ3Bsb3QoZGF0YSA9IGRlbnNlX3NldCwgYWVzKHggPSBWMywgeSA9IFY0LCBjb2xvciA9IGZhY3Rvcihtb2RlbC5rbm4uMikpKSArCiAgICAgICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMikgKwogICAgICAgIGdlb21fcG9pbnQoZGF0YSA9IHRyYWluX3dkYmMsIGFlcyh4ID0gVjMsIHkgPSBWNCwgY29sb3IgPSBmYWN0b3IoVjIpKSkgKwogICAgICAgIHNjYWxlX2NvbG9yX2Rpc2NyZXRlKG5hbWUgPSAiUHJlZGljdGlvbiIpICsKICAgICAgICBnZ3RpdGxlKCJLTk4gRGVjaXNpb24gQm91bmRhcnkgKGs9MikiKSArCiAgICAgICAgeGxhYigiQXZlcmFnZSByYWRpdXMiKSArIHlsYWIoIkF2ZXJhZ2UgdGV4dHVyZSIpCgpgYGAKCmBgYHtyIDRiX2szLCBlY2hvPUZBTFNFLCBvdXQud2lkdGggPSAiNzAlIiwgZmlnLmFsaWduID0gJ2NlbnRlcid9CiMjIGs9Mwptb2RlbC5rbm4uMyA9IGtubih0cmFpbiA9IHRyYWluX3dkYmNbLGMoIlYzIiwgIlY0IildLCAKICAgICAgICAgICAgICAgICAgdGVzdCA9IGRlbnNlX3NldFssYygiVjMiLCAiVjQiKV0sIAogICAgICAgICAgICAgICAgICBjbCA9IHRyYWluX3dkYmMkVjIsIAogICAgICAgICAgICAgICAgICBrID0gMykKCiMjIFBsb3QgCmdncGxvdChkYXRhID0gZGVuc2Vfc2V0LCBhZXMoeCA9IFYzLCB5ID0gVjQsIGNvbG9yID0gZmFjdG9yKG1vZGVsLmtubi4zKSkpICsKICAgICAgICBnZW9tX3BvaW50KGFscGhhID0gMC4yKSArCiAgICAgICAgZ2VvbV9wb2ludChkYXRhID0gdHJhaW5fd2RiYywgYWVzKHggPSBWMywgeSA9IFY0LCBjb2xvciA9IGZhY3RvcihWMikpKSArCiAgICAgICAgc2NhbGVfY29sb3JfZGlzY3JldGUobmFtZSA9ICJQcmVkaWN0aW9uIikgKwogICAgICAgIGdndGl0bGUoIktOTiBEZWNpc2lvbiBCb3VuZGFyeSAoaz0zKSIpICsKICAgICAgICB4bGFiKCJBdmVyYWdlIHJhZGl1cyIpICsgeWxhYigiQXZlcmFnZSB0ZXh0dXJlIikKCmBgYAoKYGBge3IgNGJfazQsIGVjaG89RkFMU0UsIG91dC53aWR0aCA9ICI3MCUiLCBmaWcuYWxpZ24gPSAnY2VudGVyJ30KIyMgaz00Cm1vZGVsLmtubi40ID0ga25uKHRyYWluID0gdHJhaW5fd2RiY1ssYygiVjMiLCAiVjQiKV0sIAogICAgICAgICAgICAgICAgICB0ZXN0ID0gZGVuc2Vfc2V0WyxjKCJWMyIsICJWNCIpXSwgCiAgICAgICAgICAgICAgICAgIGNsID0gdHJhaW5fd2RiYyRWMiwgCiAgICAgICAgICAgICAgICAgIGsgPSA0KQoKIyMgUGxvdCAKZ2dwbG90KGRhdGEgPSBkZW5zZV9zZXQsIGFlcyh4ID0gVjMsIHkgPSBWNCwgY29sb3IgPSBmYWN0b3IobW9kZWwua25uLjQpKSkgKwogICAgICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjIpICsKICAgICAgICBnZW9tX3BvaW50KGRhdGEgPSB0cmFpbl93ZGJjLCBhZXMoeCA9IFYzLCB5ID0gVjQsIGNvbG9yID0gZmFjdG9yKFYyKSkpICsKICAgICAgICBzY2FsZV9jb2xvcl9kaXNjcmV0ZShuYW1lID0gIlByZWRpY3Rpb24iKSArCiAgICAgICAgZ2d0aXRsZSgiS05OIERlY2lzaW9uIEJvdW5kYXJ5IChrPTQpIikgKwogICAgICAgIHhsYWIoIkF2ZXJhZ2UgcmFkaXVzIikgKyB5bGFiKCJBdmVyYWdlIHRleHR1cmUiKQoKYGBgCgpgYGB7ciA0Yl9rMjAsIGVjaG89RkFMU0UsIG91dC53aWR0aCA9ICI3MCUiLCBmaWcuYWxpZ24gPSAnY2VudGVyJ30KIyMgaz0yMAptb2RlbC5rbm4uMjAgPSBrbm4odHJhaW4gPSB0cmFpbl93ZGJjWyxjKCJWMyIsICJWNCIpXSwgCiAgICAgICAgICAgICAgICAgIHRlc3QgPSBkZW5zZV9zZXRbLGMoIlYzIiwgIlY0IildLCAKICAgICAgICAgICAgICAgICAgY2wgPSB0cmFpbl93ZGJjJFYyLCAKICAgICAgICAgICAgICAgICAgayA9IDIwKQoKIyMgUGxvdCAKZ2dwbG90KGRhdGEgPSBkZW5zZV9zZXQsIGFlcyh4ID0gVjMsIHkgPSBWNCwgY29sb3IgPSBmYWN0b3IobW9kZWwua25uLjIwKSkpICsKICAgICAgICBnZW9tX3BvaW50KGFscGhhID0gMC4yKSArCiAgICAgICAgZ2VvbV9wb2ludChkYXRhID0gdHJhaW5fd2RiYywgYWVzKHggPSBWMywgeSA9IFY0LCBjb2xvciA9IGZhY3RvcihWMikpKSArCiAgICAgICAgc2NhbGVfY29sb3JfZGlzY3JldGUobmFtZSA9ICJQcmVkaWN0aW9uIikgKwogICAgICAgIGdndGl0bGUoIktOTiBEZWNpc2lvbiBCb3VuZGFyeSAoaz0yMCkiKSArCiAgICAgICAgeGxhYigiQXZlcmFnZSByYWRpdXMiKSArIHlsYWIoIkF2ZXJhZ2UgdGV4dHVyZSIpCgpgYGAKCioqYy5Db21wdXRlIGFuZCBwbG90IHRoZSBwcmVkaWN0aW9uIGFjY3VyYWN5IChvbiBib3RoIHRoZSB0cmFpbmluZyBhbmQgdGVzdCBzZXQpLCBmb3IgayA9IHsxLDIsLi4uLDE5LDIwfSAobnVtYmVyIG9mIG5laWdoYm9ycykuIFdoaWNoIHZhbHVlIG9mIGsgd291bGQgeW91IGNob29zZT8gQ29tbWVudCBvbiB0aGUgcmVzdWx0cy4qKgoKSSB3b3VsZCBjaG9vc2Ugaz0zLiBUaGUgZm9sbG93aW5ncyBhcmUgbXkgcmVhc29ucy4gQmFzZWQgb24gdGhlIHBsb3Qgb2YgcHJlZGljdGlvbiBhY2N1cmFjeSBmb3IgZGlmZmVyZW50IHZhbHVlIG9mIGsgb24gdGhlIHRyYWluaW5nIHNldCwgd2UgY2FuIHNlZSB0aGF0IHRoZSBhY2N1cmFjeSBkZWNyZWFzZSBhcyB0aGUgayB2YWx1ZSBpbmNyZWFzZXMuIFdpdGggdGhlIGVsYm93IG1ldGhvZCwgdGhlIGVsYm93IHBvaW50IGlzIGF0IGs9MiBvciBrPTMuIEluIGFkZGl0aW9uLCBiYXNlZCBvbiB0aGUgcHJlZGljdGlvbiBhY2N1cmFjeSBmb3IgZGlmZmVyZW50IHZhbHVlcyBvZiBrIG9uIHRoZSB0ZXN0aW5nLCB3ZSBjYW4gc2VlIHRoYXQgdGhlIGFjY3VyYWN5IGF0IGs9Mywgaz02LCBrPTE3LCBrPTE4LCBrPTE5LCBhbmQgaz0yMCBhcmUgcmVsYXRpdmVseSBoaWdoLiBUaGVyZWZvcmUsIEkgd291bGQgY2hvb3NlIGs9MyBhcyB0aGUgYmVzdCBrIHZhbHVlLgoKYGBge3IgNGMsIHJlc3VsdHM9J2hpZGUnfQojIy0tLS0tLS0tLS0tLS0tLS0tLQojIyBjLgojIyBwbG90IHRoZSBwcmVkaWN0aW9uIGFjY3VyYWN5IAp0cmFpbl9hY2N1cmFjeSA9IG51bWVyaWMoMjApCnRlc3RfYWNjdXJhY3kgPSBudW1lcmljKDIwKQoKZm9yIChpIGluIDE6MjApIHsKICAgICAgICBrbm4ubGFiZWwgPC0ga25uKHRyYWluID0gdHJhaW5fd2RiY1ssYygiVjMiLCJWNCIpXSwgCiAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSB0cmFpbl93ZGJjWyxjKCJWMyIsIlY0IildLCAKICAgICAgICAgICAgICAgICAgICAgICAgY2wgPSB0cmFpbl93ZGJjJFYyLCBrID0gaSkKICAgICAgICB0cmFpbl9hY2N1cmFjeVtpXSA8LSBtZWFuKGtubi5sYWJlbCA9PSB0cmFpbl93ZGJjJFYyKQogICAgICAgIHRlc3RfYWNjdXJhY3lbaV0gPC0gbWVhbihrbm4ubGFiZWwgPT0gdGVzdF93ZGJjJFYyKQp9Cgpmb3IgKGkgaW4gMToyMCkgewogICAgICAgIGtubi5sYWJlbCA8LSBrbm4odHJhaW4gPSB0cmFpbl93ZGJjWyxjKCJWMyIsICJWNCIpXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdF93ZGJjWyxjKCJWMyIsICJWNCIpXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICBjbCA9IHRyYWluX3dkYmMkVjIsIGsgPSBpKQogICAgICAgIHRlc3RfYWNjdXJhY3lbaV0gPC0gbWVhbihrbm4ubGFiZWwgPT0gdGVzdF93ZGJjJFYyKQp9CgpgYGAKCmBgYHtyIDRjX3AxLCBlY2hvID0gRkFMU0UsIG91dC53aWR0aCA9ICI3MCUiLCBmaWcuYWxpZ24gPSAnY2VudGVyJ30KIyMgdHJhaW4gc2V0CnBsb3QoMToyMCwgdHJhaW5fYWNjdXJhY3ksIHR5cGU9ImwiLCBjb2w9ImJsdWUiLAogICAgIHhsYWI9ImsiLCB5bGFiPSJhY2N1cmFjeSIsCiAgICAgbWFpbj0iUHJlZGljdGlvbiBhY2N1cmFjeSBmb3IgZGlmZmVyZW50IHZhbHVlIG9mIGsgKHRyYWluIHNldCkiKQoKYGBgCgpgYGB7ciA0Y19wMiwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGggPSAiNzAlIiwgZmlnLmFsaWduID0gJ2NlbnRlcid9CiMjIHRlc3Qgc2V0CnBsb3QoMToyMCwgdGVzdF9hY2N1cmFjeSwgdHlwZT0ibCIsIGNvbD0icmVkIiwKICAgICB4bGFiPSJrIiwgeWxhYj0iYWNjdXJhY3kiLAogICAgIG1haW49IlByZWRpY3Rpb24gYWNjdXJhY3kgZm9yIGRpZmZlcmVudCB2YWx1ZSBvZiBrICh0ZXN0IHNldCkiKQoKYGBgCgojIyBDb2RlIEFwcGVuZGl4CgpgYGB7ciwgcmVmLmxhYmVsPWtuaXRyOjphbGxfbGFiZWxzKCksZWNobz1UUlVFLGV2YWw9RkFMU0V9CmBgYAo=